Wrap text within circle
Solution 1
Here is the best I could do.
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:
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
vladiim
Updated on June 25, 2022Comments
-
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');
-
Pablo Navarro over 10 yearsSadly, 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 over 10 yearsYes, but you need fairly large circle. In other words, the method doesn't efficiently use the circle.
-
vladiim over 10 yearsAgreed, 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 over 10 yearsYou are correct. BTW, its amazing that nobody solved it by now (I mean all those years...). Very good question!
-
bformet almost 9 yearsI 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 over 6 yearsKeep in mind that
foreignObject
does not work with IE. -
Olivvv about 6 yearsthis 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 over 2 yearsWhy this is the best solution ? How did you make it ? Can you explain by editing your answer :)