pingo
pingo

Reputation: 167

Center the curved textPath inside an arc

Given a curved textpath which follows a given arc, how can said textpah be centered inside the arc?

enter image description here

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

Answers (2)

Ryan H.
Ryan H.

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

Gilsha
Gilsha

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

Related Questions