Can d3's transition and rotation play well tog

2019-05-26 09:48发布

Transitions in combination with rotations have odd results.

Here is a fiddle with my problem: http://jsfiddle.net/emperorz/E3G3z/1/ Try clicking on each square to see the varying behaviour.

Please forgive the hacked code, but if I use transition with rotation (and x/y placement) then it loops about.

I have tried:

1) all in the transform (rotate then translate), and that seems mostly okay. A little wobbly.

2) just rotate in the transform, positioned using x/y attributes. Flies all over the place, but ends up at the correct spot. Very weird.

3) all in the transform (translate then rotate), flies away, and ends up in the (completely) wrong place.

Hmmm. Strange.

Is there a correct approach to rotating shapes with transitions?

Intuitively, it would be good if the second option worked.

Thanks

2条回答
劳资没心,怎么记你
2楼-- · 2019-05-26 10:23

To rotate an SVG object on an arbitrary axis, you need two transformations: translate (to set the axis) and rotate. What you really want is to apply the translate fully first and then rotate the already moved element, but it appears that translate and rotate operate independently and simultaneously. This ends at the right place, but animating the translate is essentially moving the axis during rotation, creating the wobble. You can isolate the translate from the rotate by having them occur at separate places in the SVG element hierarchy. For example, take a look at the following:

<g class="outer">
    <g class="rect-container">
        <rect class="rotate-me" width=200 height=100 />
    </g>
</g>

You can center the <rect> on (0,0) with translate (-100, -50). It will wobble if you apply your rotation to the <rect> element, but it will rotate cleanly if you rotate the g.rect-container element. If you want to reposition, scale, or otherwise transform the element further, do so on g.outer. That's it. You now have full control of your transforms.

Finding a <rect>'s center is easy, but finding the center of a <path>, <g>, etc. is much harder. Luckily, a simple solution is available in the .getBBox() method (code in CoffeeScript; see below for a JavaScript version*):

centerToOrigin = (el) ->
    boundingBox = el.getBBox()
    return {
        x: -1 * Math.floor(boundingBox.width/2),
        y: -1 * Math.floor(boundingBox.height/2) 
    }

You can now center your element/group by passing the non-wrapped element (using D3's .node() method)

group = d3.select("g.rotate-me")
center = centerToOrigin(group.node())
group.attr("transform", "translate(#{center.x}, #{center.y})")

For code that implements this on a both a single <rect> and <g> of of 2 rects with repositioning and scaling, see this fiddle.


*Javascript of the above code:

var center, centerToOrigin, group;

centerToOrigin = function(el) {
  var boundingBox;
  boundingBox = el.getBBox();
  return {
    x: -1 * Math.floor(boundingBox.width / 2),
    y: -1 * Math.floor(boundingBox.height / 2)
  };
};

group = d3.select("g.rotate-me");
center = centerToOrigin(group.node());
group.attr("transform", "translate(" + center.x + ", " + center.y + ")");
查看更多
甜甜的少女心
3楼-- · 2019-05-26 10:45

iirc translate is relative to 0,0 whereas rotate is around the center point of the object

As such, because your shapes are offset from 0,0 (e.g. 100,200, or 200,100) they end up migrating when translated. This can be seen by changing the offsets for Diamond3 to [50,50] - much smaller migration around the screen

The solution would be rebase the 0,0 point to the center of the diamond. There is a way to do this in D3 - but I can't remember what it is off the top of my head :(

查看更多
登录 后发表回答