SVG: reverse children rotation on parent transform - javascript

I'm fiddling around with D3.js in this plunker.
It mostly does what I want, but I noticed a small inconsistency.
The idea is that the 2nd slider rotates the SVG elements in the container. As the elements within are actually text elements, I would like to have them displayed unrotated, and as such, I applied a transform: rotate to them with the symmetric value from the slider.
Although, I noticed that by doing that, the text elements don't rotate around their center, but rather around their top-left corner (I think). This is mostly visible when you observe a point near the edge and see how in transposes the edge on rotation.
I tried already setting both text-anchor and alignment-baseline to middle on these elements, hoping it would offset the text path, but apparently it doesn't change the point around which they pivot when rotated.
Additionally, should I try to set the rotate with pivot point coordinates, it boggles the effect entirely, by applying some translate to the elements, which I can't figure out.
Not sure if getBBox() could help me either in this subject, but I've considered maybe offsetting the points by half their width/height. This seems a bit convoluted though, and I was hoping for an easier/more elegant/cleaner fix.
Any thoughts?

Well, this is awkward. Right after I posted the question, I found an answer in D3.js docs: polygon.centroid().
Basically, I used the return value of this function as the transform: rotate pivot point coordinates and the offset is taken care of. Example (line 99 of the above plunker, rotate() function):
(...)
var textElement = d3.select(this);
return justTranslate+"rotate("+ -value+ textElement.centroid() +")";
(...)
Hope this helps anyone with the same issue.
EDIT: for some reason, Chrome's console says:
Uncaught TypeError: textElement.centroid is not a function
But the behavior is what I was looking for. Have no idea why.
EDIT2: ended up changing the above answer to a getBBox() approach, because the slider movement was bonked because of the previous edit error.
Changed it to this:
text.attr("transform", function(d){
var textElement = d3.select(this);
var current = textElement.attr("transform");
var alreadyRotated = current.indexOf('rotate');
var justTranslate = current.substring(0, alreadyRotated != -1 ? alreadyRotated : current.length);
var bbox = textElement.node().getBBox();
var point = [bbox.x + 0.5*bbox.width, bbox.y + 0.5*bbox.height];
return justTranslate+"rotate("+ -value +" "+ point +")";
});

Related

Parent css transform affecting d3.mouse points

I'm using a third library that is adding 'transform: translate3d(200,0,0);' to a parent element. So the elements within the svg tend to move to the right. For drag&drop events, setting the origin is enough, but I'm having troubles to get corrected the d3.mouse(this).
I have created a jsfiddle of this example: http://bl.ocks.org/hlucasfranca/f133da4493553963e710
jsFiddle: https://jsfiddle.net/rahpuser/5jp6123u/
So, when clicking without applying the transform to the parent, everything is ok, but when the parent has the transform, d3.mouse returns a wrong number
var coords = d3.mouse(this);
coords[0] === 100; //true without transform..
coords[0] === 300; // true when the transform to the parent is applied.
// the values are not precise but you can verify the behavior in the jsfiddle
Why d3.mouse(this) is not able to get the correct value ?
my understanding is that d3.mouse should get the coords based on the container this ( svg ).
What should I do to fix the behavior keeping the parent with the new transform?
Thanks.
UPDATE:
Not working in Firefox 46 for ubuntu
Working well in chrome and opera
As you've discovered, this is a known FireFox bug. Even if you did fix FireFox, it's going to take some time to propagate, and it'll never be backwards-fixed. So you still need a workaround.
One thing you can do — as long as the SVG's top-left is always at the 0,0 coordinate of its containing div (as is the case with your jsFiddle) — is replace the call to d3.mouse(this) with:
d3.mouse(this.parentNode)
That'll get the mouse coordinate from the parent of the SVG, which is a normal div, so apparently it's not affected by this bug and will get you the value you want on all platforms.
This does not address the root problem (for example getClientBoundingRect would still return the wrong values), but it works around your specific problem.

Resizing and rotation on svg (Raphael.js) creates jumping

I've been working on this problem for days. I am trying to implement a "free transform" tool for svgs. Similar to that of Raphael.FreeTransform or how you would move/rotate/scale images in MS Word. (Yes, I am aware there are libraries) The following jSFiddle displays my problem: https://jsfiddle.net/hLjvrep7/12/
There are 5 functions in the jsFiddle: rotate-t. shrink-t, grow-t, shrink, grow. The functions suffixed with '-t' also apply the current rotation transformation. e.g.:
grow-t
rect.attr({height : height * 1.25, width : width * 1.25}).transform('r' + degree);
grow
rect.attr({height : height * 1.25, width : width * 1.25});
Once an svg is rotated, then scaled. If you try to rotate the svg again (after scale), the svg jumps. To see this, go top the fiddle:
Hit rotate-t twice. Svg should rotate a total of 30 degrees from the rectangles origin.
Hit grow (not grow-t) twice. Note the top left position of the svg stays the same.
Hit rotate-t once. Note the svg jumps to a different position, then rotates.
Note hitting rotate-t subsequent times will continue to rotate the image around the origin (which is what I want the first time rotate-t is clicked)
One solution I had was to apply the current rotation transformation whenever changing the height and width. This fixes my previous problem, but introduces another problem. To see an example of this, go to the fiddle, and:
Hit rotate-t twice.
Hit grow-t a couple times. Notice the svg grows, but the top left position of the rectangle moves. That's a problem for me. I want the svg to grow without the top left corner to move.
Notes on using the jsFiddle:
Any combination of rotate-t, grow-t, shrink-t will exhibit the ideal rotation behavior (about the origin, no jumping). But this also demonstrates the undesired growing and shrinking (top left position moved when svg is on angle).
Any combination pf rotate-t, grow, shrink will exhibit the ideal scaling behavior (top left corner of svg doesn't move). But this also demonstrates the undesired rotation property (will jump around after different rotations and scales).
Bottom line: I want to be able to the svg rotate around the origin. Then grow the image, while the top left position remains the same. Then rotate the svg again, around the origin without any jumping.
I am aware the how the transform function impacts the local coordinate system of the svg. I'm leaning towards using rotate-t, grow, shrink combo and simply apply some x-y offsets to remove the "jumping" effect. I would imagine there must be some sort of offset I could apply to avoid jumping or shifting during rotation or scaling, but its not clear to me how to calculate such offsets. Any help would be appreciated.
Please don't hesitate to ask anymore questions. Like I said, I've been digging into this for days. Clearly, I don't understand it all, but am somewhat intimate with what's happening and happy to explain anything in more detail.
My solutions for scale, rotate, move back and front etc:
$scope.back = function () {
if($scope.currentImage !==null) {
if($scope.currentImage.prev!=undefined) {
var bot = $scope.currentImage.prev;
$scope.currentImage.insertBefore(bot);
ft.apply();
}
}
};
//Function for moving front
$scope.front = function () {
if($scope.currentImage !==null) {
if($scope.currentImage.next!=undefined) {
var top = $scope.currentImage.next;
if($scope.currentImage.next.node.localName == "image")
$scope.currentImage.insertAfter(top);
ft.apply();
}
}
};
//ZOOM
$scope.zoomIn = function () {
if ($scope.currentImage!= null) {
var ft = paper.freeTransform($scope.currentImage);
if(ft.attrs.scale.y<4) {
$scope.currentImage.toFront();
ft.attrs.scale.y = ft.attrs.scale.y *(1.1);
ft.attrs.scale.x = ft.attrs.scale.x *(1.1);
ft.apply();
ft.updateHandles();
}
}
};

SVG - Endless rotation takes wrong center point

I made a codepen with snap svg: Codepen
I try to rotate a svg-gear in an endless-loop around his own centerpoint.
This works on Internet Explorer, but fails on Mozilla-Firefox and Google-Chrome.
The center point in Chrome and Firefox seems wrong and so the gear move out of his position.
For the rotation i used following code:
function infRotate(el, time, cw) {
var box = el.getBBox();
el.transform('r0,' + box.cx + ',' + box.cy);
if (cw)
el.animate({transform: 'r360,' + box.cx + ', ' + box.cy}, time, mina.linear, infRotate.bind(null, el, time, cw));
else
el.animate({transform: 'r-360,' + box.cx + ', ' + box.cy}, time, mina.linear, infRotate.bind(null, el, time, cw));
}
What i have to do for Firefox and Chrome to find right center point?
Thanks for your help.
Found solution based on #lan's comment.
The gear was in a group, which contains a X/Y - transformation.
So I try to remove each group and layer in the svg file. To see clearly the nesting of objects, I work with the XML-Editor in Inkscape.
Each object must be moved to his original position by using relativ-transformation. Using relativ movements prevent inkscape to print out translations attributes to groups.
Steps to move object relativ in Inkscape:
Go to Edit -> Select All in All Layers
Go to Object -> Transform
In Transform panel:
Uncheck Relative move and check Apply to each object separately
Move object to target position
After clean up the svg file, firefox and chrome calculate the right values too and the gear is now rotation well (see updated code on codpen)
Maybe it exist a better solution to tell Inkscape not working with transformation-attributes, but i didn't found it yet.
So if you work with animated SVG, be sure that the file is has no unnecessary groups and layers and keep attentions on transformation.
Joel except of taking center by using box.cx and box.cy. take center by dividing width and height of container by 2 and then set with it.

Positioning SVG elements via .getBoundingClientRect() in variable-width div

I have a bit of an annoying problem.
I'm trying to position a bunch of SVG circle elements according to an existing bunch of SVG text elements that share similar properties.
The circle elements are created in a very separate process than the text elements, so positioning the new elements just using the same transforms etc. as the old one isn't a viable option.
I'm trying to use .getBoundingClientRect() to get the positions since the text elements are transformed into position (so .getBBox() isn't an option) rather than positioned by x and y attributes.
With .getBoundingClientRect(), I can get the correct size/arrangement of the new elements, but since the width of the svg-containing div is variable, there's always a bit of a weird offset that I can't quite account for.
I created a simplified example of my issue here. Resize and refresh the page to see the issue in action.
The code I use to position the circle elements is replicated below.
var circs = theSvg.selectAll("circle")
.data(theCircles)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", "#f00")
.style("opacity", 0.3)
.attr("transform", function(d){
var sizeDif = 800/(d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["width"]);
var theNum = parseInt(d.split("&")[1]);
var thePosition = theSvg.selectAll("text").filter(function(e){
return e == theNum;})[0];
var theCoords = thePosition[0].getBoundingClientRect();
var leftOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["left"];
var leftOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["left"];
var bottomOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["top"];
var bottomOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["top"];
return
"translate(" + ((theCoords["left"] - leftOffset - leftOffset2)
* sizeDif) + "," + ((theCoords["top"] - bottomOffset - bottomOffset2)
* sizeDif) + ")";
})
EDIT:
This is a very delayed update just to note that while I was unable to answer my question as stated, I was able to make a workable solution based on Paul LeBeau's suggestion to extract the transforms from the target element.
In my case, I had to use a series of consecutive transforms rather than a combination of transforming and changing the x/y position (due to certain realities of the project not represented in the linked example). But I'm happy to have found an answer!
Your example works fine for me on Chrome. But really that's only because the SVG is the only thing on the page. If I add some text above the SVG everything goes wrong.
https://jsfiddle.net/rrpfmm6d/1/
Is this the problem you are talking about?
If so, the reason is because you are making the wrong choice in using getBoundingClientRect(). It provides coordinates in screen space. It's origin is the top left of the window (or iframe in the case of jsfiddle).
You should be using getBBox(). The values it returns are in the same coordinate space as the SVG elements. It's origin is (normally) at the top left of the SVG.
In summary, use the coordinates returned by calling getBBox() on your <text> element to calculate the position for your circle. If the circles are inserted into the same SVG as the text, there will be no need to do any adjusting with the div or svg offsets.

Svg text element vertical flip

I have problem with svg text element. I want to turn it over y axis.
For this, I using scale(-1,1) function.
var t = getMatrix(element);
t.scale(-1,1);
element.transform(t);
but element seems to be the same. Transformation does not work.
If I try to turn over horizontal axis, by scale(1,-1) function. It works
Please, What I doing wrong?
If you want to create transforms on an element in svg using the transform object, it is a five(5) step process:
1.) Attach the transform to your element.
var attachTransObject=myElement.transform
2.) Make a transform list for that attached transform.
var transObjList=attachTransObject.baseVal
3.) Start a Transform request object.
var requestTransformObj=mySVG.createSVGTransform()
4.) Perform/request the desired transform.
requestTransformObj.setScale(-1,1)
5.) Append the request to the transform list for the element.
transObjList.appendItem(requestTransformObj)
This may seem a bit obtuse, but once you understand these five steps, it is quite seamless.
For example, to flip a text element around the y axis, at its x,y point would be as follows:
var x=parseFloat(myText.getAttribute("x"))
var y=parseFloat(myText.getAttribute("y"))
var attachTransObject=myText.transform
var transObjList=attachTransObject.baseVal
var requestTransformObj=mySVG.createSVGTransform()
requestTransformObj.setTranslate(x,y)
transObjList.appendItem(requestTransformObj)
var requestTransformObj=mySVG.createSVGTransform()
requestTransformObj.setScale(-1,1)
transObjList.appendItem(requestTransformObj)
var requestTransformObj=mySVG.createSVGTransform()
requestTransformObj.setTranslate(-x,-y)
transObjList.appendItem(requestTransformObj)
This would build a transformation on the text element similar to:
transform="translate(153.785 94.71) scale(-1 1) translate(-153.785 -94.71)"
Thanks for help. I forgot to say that I use the library Snap svg.
It was bug there.
If someone will have the same problem, there is solution:
https://github.com/adobe-webplatform/Snap.svg/issues/153

Categories

Resources