Wrap text within circle

12,148

Solution 1

Here is the best I could do.

enter image description here

I want to center and wrap a text inside a circle or rect in SVG. The text should remain centered (horizontal/vertical) whatever the text length.

svg {
    width: 600px;
    height: 200px;
    background-color: yellow;
}
.circle {
    background-color: blue;
    height: 100%;
    border-radius: 100%;
    text-align: center;
    line-height: 200px;
    font-size: 30px;
}
.circle span {
    line-height: normal;
    display:inline-block;
    vertical-align: middle;
    color: white;
    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
<svg>
    <foreignObject width="200" height="200" x="100" y="100" transform="translate(-100,-100)">
        <div class="circle">
            <span>Here is a</span>
        </div>
    </foreignObject>

    <foreignObject width="200" height="200" x="300" y="100" transform="translate(-100,-100)">
        <div class="circle">
            <span>Here is a paragraph</span>
        </div>
    </foreignObject>

    <foreignObject width="200" height="200" x="500" y="100" transform="translate(-100,-100)">
        <div class="circle">
            <span>Here is a paragraph that requires word wrap</span>
        </div>
    </foreignObject>
</svg>

The transform attribute is not mandatory, I'm using a translate(-r, -r) so that the (x,y) of the foreignObject is like the (cx, cy) of the SVG circle, and width, height = 2*r with r the radius.

I did this to use as nodes within a D3 force layout. I leave as an exercise to translate this snippet into javascript D3's style.

Solution 2

SVG doesn't provide text wrapping, but using foreignObject you can achieve a similar effect. Assuming that radius is the radius of the circle, we can compute the dimensions of a box that will fit inside the circle:

var side = 2 * radius * Math.cos(Math.PI / 4),
    dx = radius - side / 2;

var g = svg.append('g')
    .attr('transform', 'translate(' + [dx, dx] + ')');

g.append("foreignObject")
    .attr("width", side)
    .attr("height", side)
    .append("xhtml:body")
    .html("Lorem ipsum dolor sit amet, ...");

The group should be displaced a small amount to have the text centered. I know that this is not exactly what is asked, but it can be helpful. I wrote a small fiddle. The result will look like this:

enter image description here

Solution 3

If you add your content inside a <text> element immediately below the SVG shape, then you can use D3plus' .textwrap() function to do exactly this. I quote from the documentation:

Using d3plus.textwrap, SVG <text> elements can be broken into separate <tspan> lines, as HTML does with <div> elements.... D3plus automatically detects if there is a <rect> or <circle> element placed directly before the <text> container element in DOM, and uses that element's shape and dimensions to wrap the text. If it can't find one, or that behavior needs to be overridden, they can manually be specified using .shape( ), .width( ), and .height( ).

I've created a codepen to better illustrate this since the examples in the documentation can be a little confusing: http://codepen.io/thdoan/pen/rOPYxE

Solution 4

It's not ideal, but @Pablo.Navarro's answer led me to the following.

var svg =  d3.select('#svg')
  .append('svg')
    .attr('width', 500)
    .attr('height', 200);

var radius = 60,
    x      = 150,
    y      = 100,
    side   = 2 * radius * Math.cos(Math.PI / 4),
    dx     = radius - side / 2;

var global = svg.append('g')
  .attr('transform', 'translate(' + [ dx, dx ] + ')');

global.append('circle')
  .attr('cx', x)
  .attr('cy', y)
  .attr('r', radius);

global.append('foreignObject')
  .attr('x', x - (side/2))
  .attr('y', y - (side/2))
  .attr('width', side)
  .attr('height', side)
  .attr('color', 'red')
  .append('xhtml:p')
    .text('Text meant to fit within circle')
    .attr('style', 'text-align:center;padding:2px;margin:2px;');

Result

result

Share:
12,148
vladiim
Author by

vladiim

Updated on June 25, 2022

Comments

  • vladiim
    vladiim almost 2 years

    I'm using d3 to draw a UML diagram and would like to wrap text within the shapes drawn with d3. I've gotten as far as the code below and can't find a solution to make the text 'fit' within my shape (see image below).

    var svg =  d3.select('#svg')
        .append('svg')
            .attr('width', 500)
            .attr('height', 200);
    
    var global = svg.append('g');
    
    global.append('circle')
          .attr('cx', 150)
          .attr('cy', 100)
          .attr('r', 50);
    
    global.append('text')
      .attr('x', 150)
      .attr('y', 100)
      .attr('height', 'auto')
      .attr('text-anchor', 'middle')
      .text('Text meant to fit within circle')
      .attr('fill', 'red');
    

    result

  • Pablo Navarro
    Pablo Navarro over 10 years
    Sadly, the foreignObject element can't be centered vertically. If the text is too short, it will be aligned to the top of the box.
  • VividD
    VividD over 10 years
    Yes, but you need fairly large circle. In other words, the method doesn't efficiently use the circle.
  • vladiim
    vladiim over 10 years
    Agreed, It's not ideal but gives the result I wanted. Still hopeful that the correct answer comes along. Ultimately their needs to be a relationship between the text and circle and for them to scale accordingly.
  • VividD
    VividD over 10 years
    You are correct. BTW, its amazing that nobody solved it by now (I mean all those years...). Very good question!
  • bformet
    bformet almost 9 years
    I added my answer which is a bit better I hope. You still need to take care of the font size if the text becomes too long.
  • Matt
    Matt over 6 years
    Keep in mind that foreignObject does not work with IE.
  • Olivvv
    Olivvv about 6 years
    this is really nice, but it breaks on long words. We could truncate/hyphenate them, but the maximum possible length depends on which line within the circle this word is.
  • Elikill58
    Elikill58 over 2 years
    Why this is the best solution ? How did you make it ? Can you explain by editing your answer :)