title: How to rotate figure in D3?
body: <p>The task is to rotate figure with d3, PowerPoint-like way:</p>
  
  <p><a href="https://i.stack.imgur.com/TUYGZ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/TUYGZ.png" alt="enter image description here"></a></p>
  
  <p>Got <a href="http://embed.plnkr.co/fXT0kZcRwJTPb7wQVHpg/" rel="nofollow noreferrer">this example</a>, trying to achieve the same behaviour. Can't understand, where the mistake is - figure is shaking, is not rotating the way it should. </p>
  
  <pre><code>  function dragPointRotate(rotateHandleStartPos) {
  
      rotateHandleStartPos.x += d3.event.dx;
      rotateHandleStartPos.y += d3.event.dy;
  
      const updatedRotateCoordinates = r
  
      // calculates the difference between the current mouse position and the center line
      var angleFinal = calcAngleDeg(
        updatedRotateCoordinates,
        rotateHandleStartPos
      );
      // gets the difference of the angles to get to the final angle
      var angle =
        rotateHandleStartPos.angle +
        angleFinal -
        rotateHandleStartPos.iniAngle;
  
      // converts the values to stay inside the 360 positive
      angle %= 360;
      if (angle &lt; 0) {
        angle += 360;
      }
  
      // creates the new rotate position array
      var rotatePos = [
        angle,
        updatedRotateCoordinates.cx,
        updatedRotateCoordinates.cy,
      ];
  
      r.angle = angle
  
      d3.select(`#group`).attr('transform', `rotate(${ rotatePos })`)
  
    }
  </code></pre>
  
  <p>Here's a <a href="https://jsfiddle.net/6f70h8s2/" rel="nofollow noreferrer">JSFiddle</a> and alternatively, a snippet view:</p>
  
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="true" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>const canvas = document.getElementById('canvas')
  
  d3.select(canvas).append('svg')
              .attr('width', '500')
                   .attr('height', '500')
                   .append('g')
                   .attr('id', 'group')
                   .append('rect')
                   .attr('width', '100')
                   .attr('height', '100')
                   .attr('x', '100')
                   .attr('y', '100')
  
  d3.select('#group').append('circle')
            .attr('r', '10')
                     .attr('cx', '150')
                     .attr('cy', '80')
                     .call(d3.drag()
                       .on('start', startRotation)
                       .on('drag', rotate)
                     )
                     
    let rotateHandleStartPos,
        r = { angle: 0, cx: 0, cy: 0 }
                     
    function startRotation () {
      const target = d3.select(d3.event.sourceEvent.target)
      r.cx = getElementCenter().x
      r.cy = getElementCenter().y
      let updatedRotateCoordinates = r
     
  
      // updates the rotate handle start posistion object with
      // basic information from the model and the handles
      rotateHandleStartPos = {
        angle: r.angle, // the current angle
        x: parseFloat(target.attr('cx')), // the current cx value of the target handle
        y: parseFloat(target.attr('cy')), // the current cy value of the target handle
      };
  
      // calc the rotated top &amp; left corner
      if (rotateHandleStartPos.angle &gt; 0) {
        var correctsRotateHandleStartPos = getHandleRotatePosition(
          rotateHandleStartPos
        );
        rotateHandleStartPos.x = correctsRotateHandleStartPos.x;
        rotateHandleStartPos.y = correctsRotateHandleStartPos.y;
      }
  
      // adds the initial angle in degrees
      rotateHandleStartPos.iniAngle = calcAngleDeg(
        updatedRotateCoordinates,
        rotateHandleStartPos
      );
    }
  
    function rotate () {
      dragPointRotate(rotateHandleStartPos)
    }
  
    function getElementCenter () {
      const box = document.querySelector('#group &gt; rect').getBBox()
      return {
        x: box.x + box.width / 2,
        y: box.y + box.height / 2,
      }
    }
  
    function getHandleRotatePosition(handleStartPos) {
      // its possible to use "cx/cy" for properties
      var originalX = handleStartPos.x ? handleStartPos.x : handleStartPos.cx;
      var originalY = handleStartPos.y ? handleStartPos.y : handleStartPos.cy;
  
      // gets the updated element center, without rotatio
      var center = getElementCenter();
      // calculates the rotated handle position considering the current center as
      // pivot for rotation
      var dx = originalX - center.x;
      var dy = originalY - center.y;
      var theta = (handleStartPos.angle * Math.PI) / 180;
  
      return {
        x: dx * Math.cos(theta) - dy * Math.sin(theta) + center.x,
        y: dx * Math.sin(theta) + dy * Math.cos(theta) + center.y,
      };
    }
  
    // gets the angle in degrees between two points
    function calcAngleDeg(p1, p2) {
      var p1x = p1.x ? p1.x : p1.cx;
      var p1y = p1.y ? p1.y : p1.cy;
      return (Math.atan2(p2.y - p1y, p2.x - p1x) * 180) / Math.PI;
    }
  
    function dragPointRotate(rotateHandleStartPos) {
  
      rotateHandleStartPos.x = d3.event.x;
      rotateHandleStartPos.y = d3.event.y;
  
      const updatedRotateCoordinates = r
  
      // calculates the difference between the current mouse position and the center line
      var angleFinal = calcAngleDeg(
        updatedRotateCoordinates,
        rotateHandleStartPos
      );
      // gets the difference of the angles to get to the final angle
      var angle =
        rotateHandleStartPos.angle +
        angleFinal -
        rotateHandleStartPos.iniAngle;
  
      // converts the values to stay inside the 360 positive
      angle %= 360;
      if (angle &lt; 0) {
        angle += 360;
      }
  
      // creates the new rotate position array
      var rotatePos = [
        angle,
        updatedRotateCoordinates.cx,
        updatedRotateCoordinates.cy,
      ];
  
      r.angle = angle
  
      d3.select(`#group`).attr('transform', `rotate(${ rotatePos })`)
  
    }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;
  &lt;div id="canvas"&gt;&lt;/div&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>Thanks in advance!</p>