Skip to content

Instantly share code, notes, and snippets.

@jebeck
Last active February 18, 2024 20:32
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jebeck/196406a3486985d2b92e to your computer and use it in GitHub Desktop.
Save jebeck/196406a3486985d2b92e to your computer and use it in GitHub Desktop.
Curved Text in SVG

Curved Text in SVG

Curved text in SVG isn't too hard, as long as you understand how to define different kinds of curved <path> elements in SVG. When you have a curved <path> defined in a <defs> elements (usually just inside your root <svg>), to make the text inside a <text> element follow that <path>, all that's required is to insert a <textPath> inside the <text>, with an xlink:href attribute that links to the id of the defined <path>. The actual text for display also gets added inside the <textPath>, like so:

<text>
  <textPath xlink:href="#yourPath">
    Your text
  </textPath>
</text>

Partial Arcs

Unit circle, for reference:

Here, to define a path that is a partial arc of a circle I've used a combination of the M and a commands in the SVG path data mini-language.

m is simply moveto: when you start your path data with mx,y, you're specifying the x and y coordinates where your path will start. Here we define the starting x and y coordinates with an x of half the width (i.e., horizontal center of the SVG) minus the radius of the circle and a y of half the height (i.e., vertical center of the SVG) in order to start the curve at (-1,0) on the unit circle. This works of course because we centered our circle (within which we want to have our curved text fit) at exactly the horizontal and vertical center of the SVG.

a is a general elliptical arc command.

  1. The first two arguments are rx, ry, the radii of the ellipse. When you're drawing a circle, as we're doing here, the radii are the same, of course, so we start our arc command with a190, 190. (We don't want our text's baseline to sit directly on the circle in which we're drawing it, so we've taken 95% of the circle radius to use as the radius for our a command.)

  2. The next three arguments are the toughest to get a handle on. The first is the x-axis rotation of the ellipse. Since we're not actually drawing an ellipse, but a circle, nothing we put here will matter, so we leave it zero. The second is a boolean flag for whether to choose the longer or the shorter arc when more than one arc between the start and end points of the arc is possible, but again since we're drawing a arc, it doesn't matter what we put here; the "longer" and "shorter" arcs have the same length in the special case that an ellipse is a circle. Finally, we have the sweep-flag argument, which controls whether the arc is drawn starting from the starting point and moving counter-clockwise (the 0 option) or clockwise (the 1 option). We're starting midway up on the left of the circle and we want to draw the lower half-circular arc, so we choose 0 to draw counter-clockwise (i.e., from 6 o'clock to 3 o'clock).

  3. Finally we give the end coordinates of the arc. Since we're using the a command (for relative coordinates, versus A for absolute coordinates), our coordinate system is relative to the moveto command we began with, and we don't have to repeat the calculations that went into calculating the moveto coordinates. Instead we use the unit circle to reason that if our starting point was at unit (-1,0)---now (0,0) since it's the center of our relative universe---then we want our end point to be twice the radius for the x-coordinate because we want to move to unit (1,0) and it takes two radius lengths to do that.

Altogether, the final elliptical arc command is a190,190 0 0 0 380,0.

And the entire path definition d is m485,250 a190,190 0 0 0 380,0.

textPath Attributes

The last couple of things to note here are two of the attributes we've defined on the <textPath>. We use a combination of startOffset at 50% and setting the text-anchor to the middle option to ensure that our text is centered on the curved path.

<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>
Curved Text in SVG
</title>
<script src='http://d3js.org/d3.v3.min.js' charset='utf-8'></script>
<style type="text/css">
svg {
display: block;
margin: 0 auto;
}
circle {
fill: blue;
}
path {
fill: none;
stroke: white;
stroke-width: 1px;
}
text {
font-family: cursive;
font-size: 48px;
font-weight: bold;
text-anchor: middle;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 960, height = 500, radius = 200;
// insert the main SVG element
var svg = d3.select('body').append('svg')
.attr({
height: height,
width: width,
id: 'mainSVG'
});
// get the width of the SVG
var width = document.getElementById('mainSVG').getBoundingClientRect().width;
// path data generation depends on width and radius we've chosen
function getPathData() {
// adjust the radius a little so our text's baseline isn't sitting directly on the circle
var r = radius * 0.95;
var startX = width/2 - r;
return 'm' + startX + ',' + (height/2) + ' ' +
'a' + r + ',' + r + ' 0 0 0 ' + (2*r) + ',0';
}
// add our path definition for the curved textPath to follow
svg.append('defs')
.append('path')
.attr({
d: getPathData,
id: 'curvedTextPath'
});
// add our circle element centered within the svg
svg.append('circle')
.attr({
cx: width/2,
cy: height/2,
r: radius,
id: 'mainCircle'
});
// add our path element in the main SVG so we can see it for reference
svg.append('path')
.attr({
d: getPathData,
id: 'visiblePath'
});
// add our text with embedded textPath and link the latter to the defined #curvedTextPath
svg.append('text')
.append('textPath')
.attr({
startOffset: '50%',
'xlink:href': '#curvedTextPath'
})
.text('Hello, world!');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment