vertical text in d3 (not rotated)
Asked Answered
R

5

6

I am trying to get text to display vertically in svg using d3. I do not want to rotate it, however: I want the letters to remain horizontal, but one above the other. Setting writing-mode=tb does not seem to do anything. Here is what I tried:

svg.append("text")
  .attr("x", 1000)
  .attr("y", 400)
  .attr("id", "title")
  .attr("font-size", 50)
  .attr("style", "font-family: arial; fill: lightgreen; writing-mode: tb")
  .text("Top 100 Mentions");

The text shows up in the right location, with the right font etc, but it is horizontal.

Rhombohedron answered 15/3, 2013 at 16:17 Comment(2)
Works for me in Chrome but not Firefox: jsfiddle.net/hx5ThUnsettle
Thank you, that was the problem. I tried it in Chrome and it worked, though I need to add glyph-orientation-vertical: 0 to the style to keep the text from being rotated.Rhombohedron
M
18

When talking about rotating SVG text, there are multiple different aspects:

  • how the browser determines where to position the next character based on the previous character's position;

  • how the characters are rotated relative to the baseline (rotate attribute);

  • how the final text block as a whole is rotated relative to the coordinate system (transform attribute).

Full, rather difficult to comprehend, specs here.

The 'writing-mode' property is supposed to change the first aspect without affecting the others, but as you discovered it's not implemented at all in Firefox, while IE rotates the text but doesn't respect the horizontal-glyph rule.

Theoretically, you should be able to get the same effect by combining the rotate and transform attributes: transform the entire <text> element to rotate it into a vertical position, then reverse-rotate the individual characters to get them back to horizontal. But in practice, it gets messy...

For starters, the double rotations cause the text to be end up on the left of your (x,y) point, so if (x,y) is (0,0) it will be clipped outside the SVG without supplemental shift. Because of the transforms, you'll need a negative dy value to move the text back to the right of the anchor point.

Second, there is the fact that the rotation is applied to each character in place, the spacing of the characters isn't adjusted to account for the fact that an "l" is much taller than it is wide. So unless you're using monospace, things look pretty jumbled up. You're supposed to be able to change the letter spacing with the kerning and letterspacing properties, but browser support for those is also poor: IE11 doesn't seem to acknowledge the kerning value, and Firefox doesn't acknowledge either.

A final option is to take control of the layout yourself: use the power of d3 and the string .split("") method to break your title into single-character <tspan> elements that can be positioned one below each other and centered neatly within the <text> element. The downside is that this adds extra DOM elements, you can still select the block of text as a whole, just the same as you could select a phrase in an HTML paragraph even if each letter was styled as a separate <span>. I'm not sure if screen readers will automatically assume that there are spaces between the letters, though...

Comparison

This fiddle tries out the three ways to get horizontal characters in a vertical text label (writing-mode vs double rotate vs splitting into <tspan>s):
http://jsfiddle.net/hx5Th/11/

Code:

var svg = d3.select("body").append("svg");

//Green text, uses writing-mode property //
svg.append("text")
  .attr("x", 40)
  .attr("y", 40)
  .attr("id", "title")
  .attr("font-size", 50)
  .attr("style", "fill: lightgreen; writing-mode: tb; glyph-orientation-vertical: 0")
  .text("Top 100 Mentions");

//Black text, uses a double rotate //
svg.append("text")
  .attr("x", 40)
  .attr("y", 40)
  .attr("id", "title")
  .attr("font-size", 50)
  .attr("rotate", -90)
  .attr("dx", "1em")
  .attr("dy", "-1em")
  .attr("kerning", 0)
  .attr("letter-spacing", "0.5em")
  .attr("transform", "translate(150,0) rotate(90)")
  .text("Top 100 Mentions");

//Blue text, uses d3 to create a series of tspans//
svg.append("text")
  .attr("x", 40)
  .attr("y", 40)
  .attr("font-size", 50)
  .attr("id", "title")
  .style("fill", "blue")
  .attr("transform", "translate(300,0)")
  .attr("text-anchor", "middle")
  .selectAll("tspan")
      .data("Top 100 Mentions".split(""))
  .enter().append("tspan")
      .attr("x", 0)
      .attr("dy", "0.8em")
      .text(function(d){return d;});

Results (all on a Windows 7 system):

Chrome 33 enter image description here

IE 11 enter image description here

Firefox enter image description here

I think this is d3 for the win...

Maplemaples answered 28/2, 2014 at 3:53 Comment(1)
No kidding...one more for the gallery of @Maplemaples explains! Your fan base is on the increase :-)Hemia
G
1

html embedded in svg:

<svg
  x="0px" y="0px"
  width="200px" height="200px" 
  viewbox="0 0 200 200"
>

  <foreignObject
    x="20" y="20"
    width="160" height="160"
  >

    <!-- we must set xmlns attribute
      for foreignObject.childNode -->
    <div
      xmlns="http://www.w3.org/1999/xhtml"
      style="

        /* HTML-CSS vertical text */
        writing-mode: vertical-lr;
        text-orientation: upright;

        line-height: 2em;
        font-family: sans-serif;
        font-size: 150%;
      "
    >Hello<br/>&nbsp;World</div>

  </foreignObject>

</svg>
Glutenous answered 4/2, 2020 at 6:35 Comment(0)
L
0

You can control the position and orientation of text by binding it to a path element. Example below with a running version on jsFiddle:

var svg = d3.select("body").append("svg"),
    pi = Math.PI;

var arc = d3.svg.arc()
    .innerRadius(150)
    .outerRadius(180)
    .startAngle(0)
    .endAngle(-pi/2)

var path = svg.append("path")
    .attr("id","path1")
    .attr("d","M150 150 L150 20 Z")
    .attr("style","stroke:rgb(255,0,0);stroke-width:2")

// Add a text label.
var text = svg.append("text")
    .attr("x", 6)
    .attr("dy", 15);

text.append("textPath")
   .attr("stroke","black")
   .attr("xlink:href","#path1")
   .text("abc");
Lagasse answered 15/3, 2013 at 17:7 Comment(1)
Thanks for the reply, but that rotates the text. I tried that earlier, and also tried the standard .attr("transform", "translate(1000,250) rotate(90)", which achieves the same effect. I'd like to have the text be vertical without being rotated, though.Rhombohedron
M
0

This should do what you want (although I don't know how to express it in d3):

<text x="100" y="100" transform="rotate(90, 100, 100)" style="glyph-orientation-horizontal: 270;">Some vertical text not rotated</text>

Obviously change the x and y coords to your own values.

Madid answered 27/2, 2014 at 13:24 Comment(1)
Works in Chrome, but not IE or Firefox. Although it has the benefit (compared to using writing-mode) that at least the label as a whole gets positioned in the same place in each browser, so it doesn't look horribly "broken".Maplemaples
G
0

For Firefox, the only thing I can find that they have implemented as of FF 30 is textLength. I added a fourth append of text to AmeliaBR's jsfiddle to demonstrate. It still looks like crap thought and D3 is still the winner. http://jsfiddle.net/hx5Th/11/

//Black text, uses a double rotate //
svg.append("text")
  .attr("x", 40)
  .attr("y", 40)
  .attr("id", "title")
  .attr("font-size", 50)
  .attr("rotate", -90)
  .attr("dx", "1em")
  .attr("dy", "-1em")
  .attr("textLength", "12.8em")
  .attr("transform", "translate(450,0) rotate(90)")
  .text("Top 100 Mentions");
Goodill answered 8/7, 2014 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.