0

I'm really having trouble to understand how <text> elements are supposed to be rotated in a controllable way in <svg>. The concrete example is here:

<svg version="1.1" baseProfile="full" viewBox="0 0 1000 200" xmlns="http://www.w3.org/2000/svg" id="timeline-container">
  <line x1="11" x2="200" y1="11" y2="142.2" stroke="black"/>
  <line x1="200" x2="400" y1="142.2" y2="121" stroke="black"/>
  <line x1="400" x2="600" y1="121" y2="167.6" stroke="black"/>
  <line x1="600" x2="989" y1="167.6" y2="11" stroke="black"/>
  <rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="11" y="11" transform="translate(-10,-10)"/>
  <text text-anchor="end" x="17" y="32" transform="rotate(-90,17,32)" class="label-below-project-point" >abcdef</text>
  <rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="200" y="142.2" transform="translate(-10,-10)"/>
  <text x="206" y="121.2" transform="rotate(-90,206,121.2)" class="label-above-project-point" >abc</text>
  <rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="400" y="121" transform="translate(-10,-10)"/>
  <text x="406" y="100" transform="rotate(-90,406,100)" class="label-above-project-point" >abcdefghijklmnopq</text>
  <rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="600" y="167.6" transform="translate(-10,-10)"/>
  <text x="606" y="146.6" transform="rotate(-90,606,146.6)" class="label-above-project-point" >abcdefg</text>
  <rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="989" y="11" transform="translate(-10,-10)"/>
  <text text-anchor="end" x="995" y="32" transform="rotate(-90,995,32)" class="label-below-project-point" >abcdefghijklmnopqrstuvwxy</text>
</svg>

I wanna rotate the text elements 90 degrees to display them horizontally, and align the label in relation to the timeline's rectangle to which it refers, when a user clicks on one of the rectangular timeline points. More precisely:

  • When the leftmost timeline point is clicked, the label shall rotate 90 degrees to be displayed horizontally + be left - aligned with the left border of the left-most rectangle.

  • For the rightmost point, first do a 90 deg rotation to also yield horizontal display, & then right-align the text element with the right border of the right-most rectangle.

  • For all the intermediary points, also first 90° rotate for horizontal display, but then center-align the text in relation to the respective timeline point's center.

The <text> elements are generated programmatically with provided text labels, so they may contain any number from like 1 - 30 characters; resulting in variable widths and heights.

EXAMPLE

The rotations never behave as I expect them to. For example, when I attempt to rotate the left-most <text> element by setting the rotation origin to it's bottom left corner, and the rotation angle to 90, the <text> element gets rotated way atop of the top border of the svg. no idea why. To reproduce it:

  • apply getBBox() onto the text element to get its width W.
  • add up W + the value of the y attribute of the text element to get the y coordinate of the bottom left corner of that left-most text element; Y.
  • Add the according rotation command: rotate(90,0,Y) into the transform element of the left-most text element, and you get:
  • transform="rotate(-90,17,32) rotate(90,0,Y). The rotation totally does not behave as you expect it...

EXAMPLE 2

What am I misunderstanding ? Let me also illustrate my problem with the snippet provided by @Danny '365CSI' Engelman:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start" 
           transform="rotate(-90 25 50)">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>

Let's say I now want to rotate the "Hello" text element by 90 degrees to make it become horizontally displayed, with its rotation origin at the center of the text element. I thus first calculate the center of the text element as follows:

  1. Get the dimensions of the concerned element using document.getElementsByTagName("text")[0].getBBox();. This returns an SVG rect with a height of 16.38888931274414 SVG units and a width of 36.10185241699219 SVG units.

  2. I now calculate the coordinates of the text element's center, like so:

x = 25 - 16.38888931274414 / 2 = approx. 16.81

y = 50 - 36.10185241699219 / 2 = approx. 31.95

  1. I now add rotate(90 16.81 31.95) into the transform attribute, and then totally not get the 90° rotation of the text element as expected; as you can see here:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start" 
           transform="rotate(-90 25 50) rotate(90 16.81 31.95)">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>

What am I not understanding here / How can I calculate rotation centers + thus predict rotations correctly?????????

DevelJoe
  • 856
  • 1
  • 10
  • 24
  • If you want to display text vertically there's a much easier way using writing-mode and text-orientation. Otherwise it rather depends on how the text came to be vertical as to how to rotate it. I.e. provide a [mcve] showing it in the orientation you want to rotate it from. – Robert Longson Mar 30 '21 at 22:51
  • Tried to simplify the question and the code of my snippet as far as possible. Any clue @RobertLongson? – DevelJoe Mar 31 '21 at 07:46

2 Answers2

1

Rotation also depends on text-anchor and other text positioning attributes.

But be aware; the transform does not transform the BBox,
so the :hover works on where the text was

Also see: SVG textpath, determine when text goes beyond the path

Playground:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
  text:hover{ transform:rotate(0)}
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start" 
           transform="rotate(-90 25 50)">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • Well the condition for this approach to work is to use the text-anchor exclusively in function of the rotations I want to make? How would you then for example place the label of the first timeline point? such that it appears at a fixed distance in vertical direction as in my snippet, with a text-anchor which would thus need to be "start", considering that the the text's width may variate among 1 - 30 Characters? The only solution I see for this is using calculations with ```getComputedTextLength()``` etc.; but I'd prefer an entire server-side calculated solution.. Any ideas? – DevelJoe Mar 31 '21 at 11:48
  • An entire server-side calculated solution, such that the graphic incl. labels is ready to be displayed on page load, without any additional calculations + movements of the labels needed to be made via js, which would be visible to the user on page load (hiding them is not an option).. – DevelJoe Mar 31 '21 at 12:08
  • Wait, this actually means that, even if you specify a rotation origin within ```rotate()```, it's still the coordinates according to the ```text-anchor``` which are being used for the rotation? – DevelJoe Mar 31 '21 at 13:33
  • Copy the code to a JSFiddle or CodePen or... and play with it – Danny '365CSI' Engelman Mar 31 '21 at 16:46
  • Also see: https://stackoverflow.com/questions/6800478/svg-textpath-determine-when-text-goes-beyond-the-path/66817632#66817632 – Danny '365CSI' Engelman Mar 31 '21 at 16:55
  • I feel like you didn't really understand my point; please check the added Example 2 in my question; where I describe my exact issue with your snippet. – DevelJoe Mar 31 '21 at 19:03
0

Ok, GOT IT! Mainly thanks to this. Actually, when you do two subsequent rotate() transforms on SVG Elements, you always need to refer to the original coordinates, before any transformation is applied, even for subsequent rotations. So, a step-by-step approach for rotating the "Hello" element as intended (see my question, Example 2):

State:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start" 
           transform="rotate(-90 25 50)">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>

The approach above was correct, but I applied to the wrong state of the "Hello" Element. To get the rotation origin being the center of it, you actually have to think back to the original state of that element; = the state before any rotation was applied to it, so:

Original State:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>

Now, following exactly the same approach as described in my question, you calculate the coordinates of the central point of the "Hello" element, using:

document.getElementsByTagName("text")[0].getBBox()

Which gives you the widths and heights:

  • width : 36.10185241699219
  • height : 16.38888931274414

and now, according to the initial state of the "Hello" Element, you calculate the center's coordinates:

  • x = x-offset + width / 2 = approx. 43.05
  • y = y-offset - height / 2 = approx. 41.81

Now you apply the desired rotation rotate(90 43.05 41.81), and voila:

<style>
  svg   { height: 180px }
  circle{ r:2px; fill:red }
</style>

<svg id=SVG viewbox="0 0 100 100">
  <rect width="100%" height="100%" fill="pink"></rect>

  <circle cx="25" cy="50"></circle>
  <text    x="25"  y="50" text-anchor="start" 
           transform="rotate(-90 25 50) rotate(90 43.05 41.81)">Hello</text>

  <circle cx="50" cy="50"></circle>
  <text    x="50"  y="50" text-anchor="end" dominant-baseline="hanging" 
           transform="rotate(90 50 50)">SVG</text>

  <circle cx="75" cy="50"></circle>
  <text    x="75"  y="50" text-anchor="middle" dominant-baseline="middle" 
           transform="rotate(-45 75 50)">World</text>
</svg>
DevelJoe
  • 856
  • 1
  • 10
  • 24
  • That's is what I said in my answer.. *Be aware: the transform does not transform the BBox*... You are still not there..if you set ``text-anchor="end"`` the position is wrong. If **attributes** have an influence on the rotation point, then you need to include those **attributes** in your rotation point calculation. – Danny '365CSI' Engelman Apr 01 '21 at 08:22
  • The modified calculations in my answer vs my question precisely correspond to considering the INITIAL positioning of the text - anchor in my calculations, which I didn't do first, hence I upvoted your answer. Setting a different text-anchor & then say that my calculations considering the ```start``` text-anchor are wrong is, well... That the transform does not transform the BBox had nothing to do with the actual problem, which is that the rotation does not rotate the rotated element's x-y system with it. If the definition of the rotated element's x-y system is BBox for you, okay, not for me. – DevelJoe Apr 01 '21 at 08:41
  • Anyway, thanks for your help & explanations & illustrative snippets, all of my transformations & multi - rotations now perfectly work as I want them to, no matter their text-anchors, orientations, positions. etc. But of course while considering the text-anchor position for all the calculations of the rotation origins. – DevelJoe Apr 01 '21 at 08:49