Search code examples
javascriptrotationtransformtransformationinkscape

Rotate an SVG group g around an inkscape:transform-center-x/y in Javascript/Jquery


I want to programmatically rotate the hands of a clock designed in Inkscape as an SVG to make a custom designed Date/Time picker in the browser using Javascript/Jquery.

SVG:

<svg
   width="2200"
   height="1700"
   viewBox="0 0 582.08332 449.79166"
   sodipodi:docname="DateTimePicker.svg">
...
  <g
     inkscape:label="DateTimePicker"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,152.79168)">
    <g
       id="g4802"
       inkscape:label="SecondsHand"
       inkscape:transform-center-x="0.23194686"
       inkscape:transform-center-y="-22.707409"
       transform="translate(10.583327,-42.333328)">
     .... (There are some paths in here to draw the shape)
     </g>
    <g
       id="g4798"
       inkscape:label="MinutesHand"
       inkscape:transform-center-x="0.050484234"
       inkscape:transform-center-y="-20.583753"
       transform="translate(10.583352,-42.333325)">
     .... (There are some paths in here to draw the shape)
     </g>
    <g
       id="g4789"
       inkscape:label="HoursHand"
       inkscape:transform-center-x="-0.29101068"
       inkscape:transform-center-y="-12.194587"
       transform="translate(10.583295,-42.333339)">
  <path
     inkscape:transform-center-y="-12.335484"
     sodipodi:nodetypes="ccccccccc"
     inkscape:transform-center-x="-0.43053568"
     inkscape:connector-curvature="0"
     id="path3199"
     d="m 424.77397,-31.058733 -2.7993,21.043385 c 0,0 -0.41499,2.6995795 0.88985,4.0317895 -0.089,0.23193 -0.1351,0.47814 -0.13589,0.72657 -1.3e-4,1.12967 0.91567,2.04548 2.04534,2.04536 1.12947,-1.6e-4 2.04496,-0.91589 2.04483,-2.04536 -1.8e-4,-0.24869 -0.0457,-0.49525 -0.13436,-0.7276 1.3035,-1.33258 0.88834,-4.0307595 0.88834,-4.0307595 z"
     style="opacity:0.778;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2507)"
     inkscape:label="ShadowHourHand" />
  <g
     inkscape:transform-center-x="5.3889177e-06"
     transform="translate(47.709026,-125.85737)"
     inkscape:transform-center-y="-10.8713"
     id="g3193"
     inkscape:label="HourHand">
    <path
       sodipodi:nodetypes="ccccccccc"
       inkscape:connector-curvature="0"
       id="path3183"
       d="m 1423.707,324.97033 -10.58,72.17225 c 0,0 -1.5685,10.20314 3.3632,15.23828 -0.3366,0.87657 -0.5106,1.80713 -0.5136,2.74609 -5e-4,4.26959 3.4608,7.73091 7.7304,7.73047 4.2688,-6.1e-4 7.729,-3.46162 7.7285,-7.73047 -7e-4,-0.93992 -0.1727,-1.87183 -0.5078,-2.75 4.9266,-5.03651 3.3575,-15.23437 3.3575,-15.23437 z"
       style="opacity:1;fill:url(#linearGradient4836);fill-opacity:1;stroke:none;stroke-width:1.13385832;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
       transform="matrix(0.26458333,0,0,0.26458333,0,11.249983)" />
    <path
       sodipodi:nodetypes="ccccccccccc"
       inkscape:connector-curvature="0"
       id="path3185"
       transform="matrix(0.26458333,0,0,0.26458333,0,11.249983)"
       d="m 1423.8672,338.32229 -6.9727,63.97654 0.2422,0.002 v 0.0215 c 8e-4,4.63516 3.1214,8.3923 6.9707,8.39257 3.8145,-0.002 6.919,-3.6962 6.9668,-8.28906 h 0.033 l -0.047,-0.67969 v -0.0176 -0.0215 z"
       style="opacity:0.7;fill:#ffe25d;fill-opacity:1;stroke:none;stroke-width:1.13385832;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3111)" />
    <path
       inkscape:connector-curvature="0"
       id="circle3187"
       d="m 378.15497,121.08581 a 1.4660022,1.4660022 0 0 1 -1.46601,1.466 1.4660022,1.4660022 0 0 1 -1.466,-1.466 1.4660022,1.4660022 0 0 1 1.466,-1.466 1.4660022,1.4660022 0 0 1 1.46601,1.466 z"
       style="opacity:1;fill:#ffe56a;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter2836)" />
    <path
       sodipodi:nodetypes="scscs"
       inkscape:connector-curvature="0"
       id="path3189"
       d="m 377.65184,117.39486 c 0,0.83592 -0.41881,1.51358 -0.93544,1.51358 -0.51663,0 -0.93544,-0.67766 -0.93544,-1.51358 0,-0.83592 0.35199,-2.54925 0.86862,-2.54925 0.51663,0 1.00226,1.71333 1.00226,2.54925 z"
       style="opacity:0.70899999;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3146)" />
    <path
       inkscape:connector-curvature="0"
       id="circle3191"
       d="m 377.173,121.08581 a 0.48403955,0.48403955 0 0 1 -0.48404,0.48404 0.48403955,0.48403955 0 0 1 -0.48403,-0.48404 0.48403955,0.48403955 0 0 1 0.48403,-0.48404 0.48403955,0.48403955 0 0 1 0.48404,0.48404 z"
       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.30000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3168)" />
  </g>
     </g>
</g>'s as needed

This is what Inkscape calculates on the HoursHand for rotation increments of 15 degrees.

{
  // 0 degrees
  transform-center: (-0.29101049, -12.194589),
  transform: translate(10.583294,-42.333341)
},
{
  transform-center: (-2.679194, -11.668645),
  transform: rotate(15,590.36335,14.32942)
},
{
  transform-center: (-5.8379232, -10.278138),
  transform: rotate(30,508.58162,-6.1159341)
},
{
  transform-center: (-8.5291975, -8.1175788),
  transform: rotate(45,480.68739,-13.089463)
},
{
  transform-center: (-10.569195, -5.2923862),
  transform: rotate(60,466.24828,-16.699228)
},
{
  transform-center: (-11.819306, -1.9982106),
  transform: rotate(75,457.17147,-18.968421)
},
{
  transform-center: (-12.194588, 0.29100973),
  transform: rotate(90,450.7532,-20.572983)
},
{
  transform-center: (-11.668649, 2.679191),
  transform: rotate(105,445.82829,-21.804206)
},
{
  transform-center: (-10.278149, 5.8379174),
  transform: rotate(120,441.80712,-22.809495)
},
{
  transform-center: (-8.1175958, 8.5291943),
  transform: rotate(135,438.35406,-23.672756)
},
{
  transform-center: (-5.292401, 10.569196),
  transform: rotate(150,435.25813,-24.446734)
},
{
  transform-center: (-1.9982232, 11.819314),
  transform: rotate(165,432.37318,-25.167967)
},
{
  transform-center: (0.2910098, 12.194597),
  transform: rotate(180,429.58653,-25.864625)
},
{
  transform-center: (2.6792046, 11.668654)
  transform: rotate(-165,426.79988,-26.561283)
},
{
  transform-center: (5.837939, 10.278141),
  transform: rotate(-150,423.91493,-27.282518)
},
{
  transform-center: (8.1175808, 8.5292134),
  transform: rotate(-135,420.819,-28.056498)
},
{
  transform-center: (10.569206, 5.293819),
  transform: rotate(-120,417.36594,-28.919761)
},
{
  transform-center: (11.819324, 1.9982024),
  transform: rotate(-105,413.34476,-29.925051)
},
{
  transform-center: (12.194597, -0.29101935),
  transform: rotate(-90,408.41985,-31.156276)
},
{
  transform-center: (11.668649, -2.6792024),
  transform: rotate(-75,402.00158,-32.760839)
},
{
  transform-center: (10.278139, -5.8379339),
  transform: rotate(-60,392.92477,-35.030033)
},
{
  transform-center: (8.1175801, -8.5292112),
  transform: rotate(-45,378.48565,-38.6398)
},
{
  transform-center: (5.2923805, -10.569206),
  transform: rotate(-30,350.59142,-45.613334)
},
{
  transform-center: (1.9982049, -11.819318),
  transform: rotate(-15,268.80967,-66.058702)
}

Here is some Javascript I ran on the HoursHand at 0 degrees:

CTM: SVGMatrix
​​  a: 0.6871868371963501
​  b: 0
  c: 0
​​  d: 0.6871868371963501
​​  e: 7.272700786590576
​​  f: 76.36006927490234
​boundingBox: SVGRect
​​  height: 28.33203125
​​  width: 6.1015625
​  x: 421.53515625
​​  y: -31.05859375
​​boundingClientRect: DOMRect
​​  bottom: 74.5
​​  height: 19.5
​​  left: 296.933349609375
​​  right: 301.15000915527344
​​  top: 55
​​  width: 4.2166595458984375
​​  x: 296.933349609375
​​  y: 55
​​0: <g id="g4789" inkscape:label="HoursHand" inkscape:transform-center-x="-0.29101068" inkscape:transform-center-y="-12.194587" transform="translate(10.583295,-42.333339)">
​​screenCTM: SVGMatrix
​​  a: 0.6871868371963501
​​  b: 0
​​  c: 0
​​  d: 0.6871868371963501
​​  e: 7.272700786590576
​​  f: 76.36006927490234
​​transform: "translate(10.583295,-42.333339)"
​transform_center_x: "-0.29101068"
​transform_center_y: "-12.194587"

Inverse CTM = (a,d = 1.45522, b,c = 0, e = -10.58339, f = -111.12089)

What I want to know is for some arbitrary object, starting with HoursHand, and any degree or rotation, how to calculate the transform-center x and y, as well as the x and y of the center of rotation, for that degree as Inkscape calculates it.

Note: The transform-center here is off center, and I want to use Javascript rather than rely on CSS since SVG transforms are handled differently than CSS transforms, so the method explained here will not work: SVG Animation rotate group around its center

Update 1: I've read what I can understand of the relevant chapter mentioned in this answer: Why am I failing to rotate a path around a given point in SVG pointing to this text: https://www.w3.org/TR/SVG/coords.html#TransformProperty which helped some.

The issue here though is that my paths coming out of Inkscape have coordinates like m 424.77397,-31.058733 in their user coordinate space. If you notice, at 15 degree rotation on the HoursHand I have rotate(15,590.36335,14.32942) and at 30 degrees I have rotate(30,508.58162,-6.1159341), the center of rotation on the transform has changed drastically on the x-axis.

So some angle-finding code needs to be applied to pre-determine where the center of rotation needs to be when rotating instead of the translate(~10,~-42) in the current user coordinate space, which is where I need help.


Solution

  • The sane way to do this in Inkscape is to select a group node that has a "transform: translate(x, y)" attribute, and remove the transform. Then select the group's child nodes and position where the group node needed. On the Javascript side I needed to remember the center point positions for transformations before rotating. The Hours, Minutes, and Seconds hand all share the same center point for rotation, which needs to be absolute positioned relative to the bounding box coordinates obtained with getBBox().

    Knowing how to do rotations in an SVG user coordinate system with a group that is translated and has a transformation center that is off-center could still be advantageous in some situations though.