Search code examples
svgtextrotation

SVG rotated and flipped text doesn't produce an accurate result


I would like to write text on SVG at different angles and flip (if angle is > 90 and < 270 degrees to get text oriented for better readable).

<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg" > 
<circle  cx="323" cy="422" r="153" fill="none" stroke="#900"  stroke-width="0.38"/>
<circle  cx="323" cy="422" r="228" fill="none" stroke="#900"  stroke-width="0.38"/>
  <g id="E.3011.40-debug" class="units" font-size="10.0" fill="#000"> 
    <line title="0" x1="323" y1="422" x2="477" y2="422" stroke="#900" stroke-width="0.38"></line>
    <text x="477" y="422" style="">way +1 0° 0° </text>
    
    <line title="15" x1="323" y1="422" x2="472" y2="462" stroke="#900" stroke-width="0.38"></line>
    <text x="472" y="462" style="transform-box:fill-box;rotate:15deg;">way +1 0° 15° </text>
    
    <line title="30" x1="323" y1="422" x2="456" y2="499" stroke="#900" stroke-width="0.38"></line>
    <text x="456" y="499" style="transform-box:fill-box;rotate:30deg;">way +1 0° 30° </text>
    
    <line title="45" x1="323" y1="422" x2="432" y2="531" stroke="#900" stroke-width="0.38"></line>
    <text x="432" y="531" style="transform-box:fill-box;rotate:45deg;">way +1 0° 45° </text>
    
    <line title="60" x1="323" y1="422" x2="400" y2="555" stroke="#900" stroke-width="0.38"></line>
    <text x="400" y="555" style="transform-box:fill-box;rotate:60deg;">way +1 0° 60° </text>
    
    <line title="75" x1="323" y1="422" x2="363" y2="571" stroke="#900" stroke-width="0.38"></line>
    <text x="363" y="571" style="transform-box:fill-box;rotate:75deg;">way +1 0° 75° </text>
    
    <line title="90" x1="323" y1="422" x2="323" y2="576" stroke="#900" stroke-width="0.38"></line>
    <text x="323" y="576" style="transform-box:fill-box;rotate:90deg;">way +1 0° 90° </text>
    
    <line title="105" x1="323" y1="422" x2="283" y2="571" stroke="#900" stroke-width="0.38"></line>
    <text x="283" y="571" style="transform-box:fill-box;rotate:105deg;">way +1 0° 105° </text>
    
    <line title="120" x1="323" y1="422" x2="246" y2="555" stroke="#900" stroke-width="0.38"></line>
    <text x="246" y="555" style="transform-box:fill-box;rotate:120deg;">way +1 0° 120° </text>
    
    <line title="135" x1="323" y1="422" x2="215" y2="531" stroke="#900" stroke-width="0.38"></line>
    <text x="215" y="531" style="transform-box:fill-box;rotate:135deg;">way +1 0° 135° </text>
    
    <line title="150" x1="323" y1="422" x2="190" y2="499" stroke="#900" stroke-width="0.38"></line>
    <text x="190" y="499" style="transform-box:fill-box;rotate:150deg;">way +1 0° 150° </text>
    
    <line title="165" x1="323" y1="422" x2="175" y2="462" stroke="#900" stroke-width="0.38"></line>
    <text x="175" y="462" style="transform-box:fill-box;rotate:165deg;">way +1 0° 165° </text>
    
    <line title="180" x1="323" y1="422" x2="170" y2="422" stroke="#900" stroke-width="0.38"></line>
    <text x="170" y="422" style="transform-box:fill-box;rotate:180deg;">way +1 0° 180° </text>
    
    <line title="195" x1="323" y1="422" x2="175" y2="383" stroke="#900" stroke-width="0.38"></line>
    <text x="175" y="383" style="transform-box:fill-box;rotate:195deg;">way +1 0° 195° </text>
    
    <line title="210" x1="323" y1="422" x2="190" y2="346" stroke="#900" stroke-width="1"></line>
    <text x="190" y="346" style="transform-box:fill-box;rotate:210deg;transform:scale(-1,-1);">way +1 0° 210° </text>
    
    <line title="225" x1="323" y1="422" x2="215" y2="314" stroke="#900" stroke-width="0.38"></line>
    <text x="215" y="314" style="transform-box:fill-box;rotate:225deg;">way +1 0° 225° </text>
    
    <line title="240" x1="323" y1="422" x2="246" y2="289" stroke="#900" stroke-width="0.38"></line>
    <text x="246" y="289" style="transform-box:fill-box;rotate:240deg;">way +1 0° 240° </text>
    
    <line title="255" x1="323" y1="422" x2="283" y2="274" stroke="#900" stroke-width="0.38"></line>
    <text x="283" y="274" style="transform-box:fill-box;rotate:255deg;">way +1 0° 255° </text>
  </g>
</svg>

jsfiddle

For this purpose I generated a demo SVG with some line and text on different angles, as you can see the text is not well aligned except the 90degrees (that correctly aligns, obviously, on the text baseline). Also I tried to flip the text at 210 degrees with the "transform:scale(-1,-1);" getting unexpected result.

Despite I tried many combination of attribute "alignment-baseline" and "text-anchor" I get worst results, how could I get texts aligned correctly?


Solution

  • As commented by enxaneta you could take advantage of SVG's transform function rotate(angle x y) as it allows to specify a transform origin point as 2. and 3. argument.

    Here's a little JS helper converting your css transformations to attributes:

    let texts = document.querySelectorAll('text');
    texts.forEach(text => {
      let angle = text.style.rotate ? parseFloat(text.style.rotate) : 0;
      // change rotation and text alignment for angles >90° 
      if(angle>=90){
        angle -=180;
        text.setAttribute('text-anchor', `end`)
      }
      let [x, y] = [+text.getAttribute('x'), +text.getAttribute('y')];
      // set svg transform attribute
      text.setAttribute('transform', `rotate(${angle} ${x} ${y})`)
      text.style.removeProperty('transform-box')
      text.style.removeProperty('rotate')
    })
    

    Quite likely, you're dynamically generating your diagram - so you can just adapt your script to replace the current transformation properties whil.

    let texts = document.querySelectorAll('text');
    texts.forEach(text => {
      let angle = text.style.rotate ? parseFloat(text.style.rotate) : 0;
      // change rotation and text alignment for angles >90° 
      if(angle>=90){
        angle -=180;
        text.setAttribute('text-anchor', `end`)
      }
      let [x, y] = [+text.getAttribute('x'), +text.getAttribute('y')];
      // set svg transform attribute
      text.setAttribute('transform', `rotate(${angle} ${x} ${y})`)
      text.style.removeProperty('transform-box')
      text.style.removeProperty('rotate')
    })
    <svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
            <circle cx="323" cy="422" r="153" fill="none" stroke="#900" stroke-width="0.38" />
            <circle cx="323" cy="422" r="228" fill="none" stroke="#900" stroke-width="0.38" />
            <g id="E.3011.40-debug" class="units" font-size="10.0" fill="#000">
                <line title="0" x1="323" y1="422" x2="477" y2="422" stroke="#900" stroke-width="0.38"></line>
                <text x="477" y="422" style="">way +1 0° 0° </text>
    
                <line title="15" x1="323" y1="422" x2="472" y2="462" stroke="#900" stroke-width="0.38"></line>
                <text x="472" y="462" style="transform-box:fill-box;rotate:15deg;">way +1 0° 15° </text>
    
                <line title="30" x1="323" y1="422" x2="456" y2="499" stroke="#900" stroke-width="0.38"></line>
                <text x="456" y="499" style="transform-box:fill-box;rotate:30deg;">way +1 0° 30° </text>
    
                <line title="45" x1="323" y1="422" x2="432" y2="531" stroke="#900" stroke-width="0.38"></line>
                <text x="432" y="531" style="transform-box:fill-box;rotate:45deg;">way +1 0° 45° </text>
    
                <line title="60" x1="323" y1="422" x2="400" y2="555" stroke="#900" stroke-width="0.38"></line>
                <text x="400" y="555" style="transform-box:fill-box;rotate:60deg;">way +1 0° 60° </text>
    
                <line title="75" x1="323" y1="422" x2="363" y2="571" stroke="#900" stroke-width="0.38"></line>
                <text x="363" y="571" style="transform-box:fill-box;rotate:75deg;">way +1 0° 75° </text>
    
                <line title="90" x1="323" y1="422" x2="323" y2="576" stroke="#900" stroke-width="0.38"></line>
                <text x="323" y="576" style="transform-box:fill-box;rotate:90deg;">way +1 0° 90° </text>
    
                <line title="105" x1="323" y1="422" x2="283" y2="571" stroke="#900" stroke-width="0.38"></line>
                <text x="283" y="571" style="transform-box:fill-box;rotate:105deg;">way +1 0° 105° </text>
    
                <line title="120" x1="323" y1="422" x2="246" y2="555" stroke="#900" stroke-width="0.38"></line>
                <text x="246" y="555" style="transform-box:fill-box;rotate:120deg;">way +1 0° 120° </text>
    
                <line title="135" x1="323" y1="422" x2="215" y2="531" stroke="#900" stroke-width="0.38"></line>
                <text x="215" y="531" style="transform-box:fill-box;rotate:135deg;">way +1 0° 135° </text>
    
                <line title="150" x1="323" y1="422" x2="190" y2="499" stroke="#900" stroke-width="0.38"></line>
                <text x="190" y="499" style="transform-box:fill-box;rotate:150deg;">way +1 0° 150° </text>
    
                <line title="165" x1="323" y1="422" x2="175" y2="462" stroke="#900" stroke-width="0.38"></line>
                <text x="175" y="462" style="transform-box:fill-box;rotate:165deg;">way +1 0° 165° </text>
    
                <line title="180" x1="323" y1="422" x2="170" y2="422" stroke="#900" stroke-width="0.38"></line>
                <text x="170" y="422" style="transform-box:fill-box;rotate:180deg;">way +1 0° 180° </text>
    
                <line title="195" x1="323" y1="422" x2="175" y2="383" stroke="#900" stroke-width="0.38"></line>
                <text x="175" y="383" style="transform-box:fill-box;rotate:195deg;">way +1 0° 195° </text>
    
                <line title="210" x1="323" y1="422" x2="190" y2="346" stroke="#900" stroke-width="1"></line>
                <text x="190" y="346" style="transform-box:fill-box;rotate:210deg;transform:scale(-1,-1);">way +1 0° 210°
                </text>
    
                <line title="225" x1="323" y1="422" x2="215" y2="314" stroke="#900" stroke-width="0.38"></line>
                <text x="215" y="314" style="transform-box:fill-box;rotate:225deg;">way +1 0° 225° </text>
    
                <line title="240" x1="323" y1="422" x2="246" y2="289" stroke="#900" stroke-width="0.38"></line>
                <text x="246" y="289" style="transform-box:fill-box;rotate:240deg;">way +1 0° 240° </text>
    
                <line title="255" x1="323" y1="422" x2="283" y2="274" stroke="#900" stroke-width="0.38"></line>
                <text x="283" y="274" style="transform-box:fill-box;rotate:255deg;">way +1 0° 255° </text>
            </g>
        </svg>

    Another benefit of using SVG's rotate() function is it's support even in less capable svg applications:
    Many graphic editors have only limited CSS capabilities - especially with regards to transform properties like transform-box or transform-origin.

    If you prefer to use CSS transforms you can emulate SVG's pivot point rotation by applying two translate() values:

    text.style.transform = `translate(${x}px, ${y}px) rotate(${angle}deg) translate(${-x}px, ${-y}px)`;
    

    let texts = document.querySelectorAll('text');
    texts.forEach(text => {
      let angle = text.style.rotate ? parseFloat(text.style.rotate) : 0;
      let [x, y] = [+text.getAttribute('x'), +text.getAttribute('y')];
    
      // change rotation and text alignment for angles >90° 
      if (angle >= 90) {
        angle -= 180;
        text.setAttribute('text-anchor', `end`)
      }
    
      // use css transform
      text.style.transform = `translate(${x}px, ${y}px) rotate(${angle}deg) translate(${-x}px, ${-y}px)`;
    
      text.style.removeProperty('transform-box')
      text.style.removeProperty('rotate')
    
    })
    <svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
            <circle cx="323" cy="422" r="153" fill="none" stroke="#900" stroke-width="0.38" />
            <circle cx="323" cy="422" r="228" fill="none" stroke="#900" stroke-width="0.38" />
            <g id="E.3011.40-debug" class="units" font-size="10.0" fill="#000">
                <line title="0" x1="323" y1="422" x2="477" y2="422" stroke="#900" stroke-width="0.38"></line>
                <text x="477" y="422" style="">way +1 0° 0° </text>
    
                <line title="15" x1="323" y1="422" x2="472" y2="462" stroke="#900" stroke-width="0.38"></line>
                <text x="472" y="462" style="transform-box:fill-box;rotate:15deg;">way +1 0° 15° </text>
    
                <line title="30" x1="323" y1="422" x2="456" y2="499" stroke="#900" stroke-width="0.38"></line>
                <text x="456" y="499" style="transform-box:fill-box;rotate:30deg;">way +1 0° 30° </text>
    
                <line title="45" x1="323" y1="422" x2="432" y2="531" stroke="#900" stroke-width="0.38"></line>
                <text x="432" y="531" style="transform-box:fill-box;rotate:45deg;">way +1 0° 45° </text>
    
                <line title="60" x1="323" y1="422" x2="400" y2="555" stroke="#900" stroke-width="0.38"></line>
                <text x="400" y="555" style="transform-box:fill-box;rotate:60deg;">way +1 0° 60° </text>
    
                <line title="75" x1="323" y1="422" x2="363" y2="571" stroke="#900" stroke-width="0.38"></line>
                <text x="363" y="571" style="transform-box:fill-box;rotate:75deg;">way +1 0° 75° </text>
    
                <line title="90" x1="323" y1="422" x2="323" y2="576" stroke="#900" stroke-width="0.38"></line>
                <text x="323" y="576" style="transform-box:fill-box;rotate:90deg;">way +1 0° 90° </text>
    
                <line title="105" x1="323" y1="422" x2="283" y2="571" stroke="#900" stroke-width="0.38"></line>
                <text x="283" y="571" style="transform-box:fill-box;rotate:105deg;">way +1 0° 105° </text>
    
                <line title="120" x1="323" y1="422" x2="246" y2="555" stroke="#900" stroke-width="0.38"></line>
                <text x="246" y="555" style="transform-box:fill-box;rotate:120deg;">way +1 0° 120° </text>
    
                <line title="135" x1="323" y1="422" x2="215" y2="531" stroke="#900" stroke-width="0.38"></line>
                <text x="215" y="531" style="transform-box:fill-box;rotate:135deg;">way +1 0° 135° </text>
    
                <line title="150" x1="323" y1="422" x2="190" y2="499" stroke="#900" stroke-width="0.38"></line>
                <text x="190" y="499" style="transform-box:fill-box;rotate:150deg;">way +1 0° 150° </text>
    
                <line title="165" x1="323" y1="422" x2="175" y2="462" stroke="#900" stroke-width="0.38"></line>
                <text x="175" y="462" style="transform-box:fill-box;rotate:165deg;">way +1 0° 165° </text>
    
                <line title="180" x1="323" y1="422" x2="170" y2="422" stroke="#900" stroke-width="0.38"></line>
                <text x="170" y="422" style="transform-box:fill-box;rotate:180deg;">way +1 0° 180° </text>
    
                <line title="195" x1="323" y1="422" x2="175" y2="383" stroke="#900" stroke-width="0.38"></line>
                <text x="175" y="383" style="transform-box:fill-box;rotate:195deg;">way +1 0° 195° </text>
    
                <line title="210" x1="323" y1="422" x2="190" y2="346" stroke="#900" stroke-width="1"></line>
                <text x="190" y="346" style="transform-box:fill-box;rotate:210deg;transform:scale(-1,-1);">way +1 0° 210°
                </text>
    
                <line title="225" x1="323" y1="422" x2="215" y2="314" stroke="#900" stroke-width="0.38"></line>
                <text x="215" y="314" style="transform-box:fill-box;rotate:225deg;">way +1 0° 225° </text>
    
                <line title="240" x1="323" y1="422" x2="246" y2="289" stroke="#900" stroke-width="0.38"></line>
                <text x="246" y="289" style="transform-box:fill-box;rotate:240deg;">way +1 0° 240° </text>
    
                <line title="255" x1="323" y1="422" x2="283" y2="274" stroke="#900" stroke-width="0.38"></line>
                <text x="283" y="274" style="transform-box:fill-box;rotate:255deg;">way +1 0° 255° </text>
            </g>
        </svg>