Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to right/end align text along an textPath inside an arc using d3.js?

Tags:

svg

d3.js

Here's the fiddle: http://jsfiddle.net/DevChefOwen/CZ6Dp/

var text = g.append("text")
            .style("font-size",30)
            .style("fill","#000")
            .attr("dy",0)
            .append("textPath")
            .attr("xlink:href","#yyy")
            .style("text-anchor","left") // using "end", the entire text disappears
            .text("some text");

I've tried a number of different things to no avail. The left align is the easy part. If you did a middle, though, you see only "text" instead of "some text", implying that "some" is just hidden because it went "out of span" for the given arc.

If, however, I added:

        .attr("startOffset","39%")

(as in here: http://jsfiddle.net/DevChefOwen/2H99c/)

It would look right aligned, but outside of programmatically trying to get the width/height of the text element and look for sharp changes in width/height (which seems wrong and likely error-prone), I can't seem to find a way to right align the text.

I've also tried using an SVG path (essentially a curved arc line) and the same disappearing act happens with the text when "text-anchor" is set to "left".

Thanks ahead for your time!

like image 488
DevChefOwen Avatar asked Mar 06 '14 22:03

DevChefOwen


3 Answers

The question is somewhat confusing matters. The issue isn't aligning text at the end of the path -- that's easy to do with "text-anchor"="end" and "startOffset"="100%".

However, using those settings with the path created by the d3 arc function, you end up with the text cornering around the end of the inside curve and the left straight edge, to the end of the path as defined by the arc function: http://jsfiddle.net/CZ6Dp/8/

The real issue is that the path that you want the text to be aligned along (the outside arc of the shape) is only one segment of the path that defines the shape.

(By the way, "left" and "right" are not valid values for the "text-anchor" property, and will just be ignored).

The answer by @defghi1977 gives one way to approach the problem, by figuring out the length of the path segment that you do want to use and adjusting the start offset accordingly.

Another way to approach the problem is to create a separate path (not drawn on screen) that represents only the part of the path that you want to be used for positioning text.

There are a number of possible ways to create a path that only represents the outside arc (some example code here). @defghi1977's approach of grabbing it from the existing path with regular expressions is probably the most efficent for your situation. But instead of just creating a temporary element to calculate a length, I actually have to add the new path to the DOM so it can be used as the reference path for the <textPath> element. (Which I suppose is the downside to this approach -- twice as many DOM elements!)

var path = g.append("svg:path")
    .attr("d", arct)
    .style("fill","#ccc")
    .attr("transform", "translate("+cfg.w/2+","+cfg.h/2+")")
    .each(function(d,i) {
        var justArc = /(^.+?)L/; 
        //grab everything up to the first Line statement
        var thisSelected = d3.select(this);
        var arcD = justArc.exec( thisSelected.attr("d") )[1];
        defs.append("path")
            .attr("id", "yyy") //normally the id would be based on the data or index  
            .attr("d", arcD)
            .attr("transform", thisSelected.attr("transform") );
            //if you can avoid using transforms directly on the path element, 
            //you'll save yourself having to repeat them for the text paths...
    });

var text = g.append("text")
            .style("font-size",30)
            .style("fill","#000")
            .attr("dy",0)
            .append("textPath")
            .attr("xlink:href","#yyy")
            .style("text-anchor","end") 
            .attr("startOffset","100%")
            .text("some text");

http://jsfiddle.net/CZ6Dp/9/

Again, factoring in the extra DOM load @defghi1977's method is probably slightly preferrable, although this version has the benefit of not being dependent on browser support for getTotalLength. But as far as I know that method is fairly well implemented.

So just consider this an alternate approach for completeness' sake.

like image 136
AmeliaBR Avatar answered Nov 15 '22 11:11

AmeliaBR


This path is constructed by 4(or 5) path segments.
So, this probrem will be solved to get first arc path length.
But I don't know how to get sub path length by using d3.js, thus I use svgdom directly.
I tried to fix your code. If this code is not what you hope, I'm sorry.

  1. path-anchor attribute to end.
  2. define function to get startOffset value.

var path = g.append("svg:path")
    .attr("id","yyy")
    .attr("d", arct)
    .style("fill","#ccc")
    .attr("transform", "translate("+cfg.w/2+","+cfg.h/2+")");

var text = g.append("text")
        .style("font-size",30)
        .style("fill","#000")
        .attr("dy",0)
        .append("textPath")
        .attr("xlink:href","#yyy")
        //.style("text-anchor","left") // using "end", the entire text disappears
        .attr("text-anchor", "end")
        .text("some text")
        .attr("startOffset",function(){
            var d = document.getElementById("yyy").getAttribute("d");
            var tmp = document.createElementNS("http://www.w3.org/2000/svg" ,"path");
            //get the arc segment of path
            var arc = d.match(/(^.+?)L/)[1];
            tmp.setAttribute("d", arc);
            //return offset position
            return tmp.getTotalLength();
        });
like image 20
defghi1977 Avatar answered Nov 15 '22 10:11

defghi1977


I think the confusion comes from the meaning of text-anchor - it's not "relative to where on the parent will I justify" but rather "what part of me should I align to the start".

You're right to try to use startOffset to move the origin. Since the outer radius of your path is longer than the inner radius, the correct start offset is a little more than half of the path (around 53%).

Just a little more twiddling with your settings and you should have it. Here's a fiddle with my interpretation of what you're looking for.

like image 30
couchand Avatar answered Nov 15 '22 11:11

couchand