score:6

Accepted answer

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 + ")");

score:0

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 :(


Related Query