Reputation: 167
Given a curved textpath which follows a given arc, how can said textpah be centered inside the arc?
Please see this example
In it I have manually set x and dy on <text> through trial and error so that the result is what I want (text is centered along the arc).
But I am generating many arcs dynamically and need a way to center the text position dynamically (depending on d).
I think the sub-question here is how d.x and d.dx relate with <text> dx and x ? Same for the y's.
Upvotes: 1
Views: 1321
Reputation: 7854
For posterity ;)
The reason why you need dy
is because TextPath
starts at the beginning of the path which is defined by the outerRadius
and draws the text so that its baseline matches the path. It will then continue to the end of this outer path and wrap around to the inner path. I've not yet been able to find or come up with a way to calculate the height of the font and adjust dy
accordingly. Alternatively, you can create a new arc to guide the text and adjust the radius to something suitable.
To automatically centre the text on the upper arc you need to set two attributes. The first is startOffset
, to offset the starting point; it should be set to 25% since the full length of the line includes both inner and outer radii. This might be slightly off since the outer arc is longer than the inner. Alternatively, you could create a second path with inner and outer radii the same and use that path for the text. Then you need to use text-anchor
and set that to middle
.
If you want to prevent the text from wrapping around from the outer to the inner arc, you can remove the inner arc from the path first and offset the text by 50% instead of 25%.
function removeInnerArc(path) {
return path.replace(/(M.*A.*)(A.*Z)/, function(_, m1) {
return m1 || path;
});
}
var arc = d3.svg.arc()
.innerRadius(180)
.outerRadius(220)
.startAngle(-Math.PI / 4)
.endAngle(function(t) {
return t * 2 * Math.PI / 3;
});
// Generate a new arc to guide the text
// The radius determines the baseline for the text
var textArc = d3.svg.arc()
.innerRadius(190)
.outerRadius(190)
.startAngle(-Math.PI / 4)
.endAngle(function(t) {
return t * 2 * Math.PI / 3;
});
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("transform", "translate(250,250)");
svg.append("defs").append("path")
.attr("id", "text-path")
.attr("d", removeInnerArc(textArc(1)));
svg.append("path")
.attr("id", "path")
.attr("d", arc(1));
svg.append("clipPath")
.attr("id", "text-clip")
.append("use")
.attr("xlink:href", "#path");
svg.append("text")
.attr("clip-path", "url(#text-clip)")
.append("textPath")
.attr("xlink:href", "#text-path")
.text("Hello, curved textPath!")
// You need the following two lines to position the text correctly
.attr("text-anchor", "middle")
.attr("startOffset", "50%");
path {
fill: #3182bd;
}
text {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 24px;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans:400,600">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
Upvotes: 1
Reputation: 14589
Use getPointAtLength method of SVG Path. Hope this helps.
var path = svg.append("path")
.attr("id", "path")
.attr("d", arc(1));
var text = "Hello, curved textPath!";
var pt = path.node().getPointAtLength(path.node().getTotalLength()/2-text.length);
svg.append("text")
.attr("clip-path", "url(#text-clip)")
.attr("x", pt.x)
.attr("dy", 28)
.append("textPath")
.attr("xlink:href", "#text-path")
.text("Hello, curved textPath!");
Upvotes: 0