Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 JS upside down path text

Tags:

svg

d3.js

Is it possible to show the text not upside down in this case?

http://jsfiddle.net/paulocoelho/Hzsm8/1/

Code:

var cfg = {
    w:400,
    h:400
};

var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")

var arct = d3.svg.arc()
        .innerRadius(cfg.h / 5)
        .outerRadius(cfg.h / 3)
        .startAngle(Math.PI/2)
        .endAngle(Math.PI*1.5);

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

var text = g.append("text")
            .style("font-size",30)
            .style("fill","#F8F8F8")
            .attr("dy",35)
            .append("textPath")
            .attr("xlink:href","#yyy")
            .attr("startOffset",50)
            .text("some text")
    ;
like image 790
PCoelho Avatar asked May 21 '13 14:05

PCoelho


2 Answers

A great example is Placing Texts on Arcs with D3.js by Nadieh Bremer. A lengthy blog with many images from which the following is an extract:

Flipping the Text on the Bottom Half

You could already feel like it’s finished with that look. But I find those labels along the bottom half, that are upside down, rather hard to read. I’d prefer it if those labels were flipped, so I can read them from left to right again.

To accomplish this, we need to switch the start and end coordinates of the current arc paths along the bottom half so they are drawn from left to right. Furthermore, the sweep-flag has to be set to 0 to get the arc that runs in a counterclockwise fashion from left to right

So for the final act, let’s add a few more lines of code to the .each() statement

//Create the new invisible arcs and flip the direction for those labels on the bottom half
.each(function(d,i) {
    //Search pattern for everything between the start and the first capital L
    var firstArcSection = /(^.+?)L/;    

    //Grab everything up to the first Line statement
    var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
    //Replace all the commas so that IE can handle it
    newArc = newArc.replace(/,/g , " ");

    //If the end angle lies beyond a quarter of a circle (90 degrees or pi/2) 
    //flip the end and start position
    if (d.endAngle > 90 * Math.PI/180) {
        var startLoc    = /M(.*?)A/,        //Everything between the capital M and first capital A
            middleLoc   = /A(.*?)0 0 1/,    //Everything between the capital A and 0 0 1
            endLoc      = /0 0 1 (.*?)$/;   //Everything between the 0 0 1 and the end of the string (denoted by $)
        //Flip the direction of the arc by switching the start and end point (and sweep flag)
        var newStart = endLoc.exec( newArc )[1];
        var newEnd = startLoc.exec( newArc )[1];
        var middleSec = middleLoc.exec( newArc )[1];

        //Build up the new arc notation, set the sweep-flag to 0
        newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
    }//if

    //Create a new invisible arc that the text can flow along
    svg.append("path")
        .attr("class", "hiddenDonutArcs")
        .attr("id", "donutArc"+i)
        .attr("d", newArc)
        .style("fill", "none");
});

The only thing that has changed since the previous section is the addition of the if statement. To flip the start and end positions, we can use a few more regular expressions. The current starting x and y location is given by everything in between the capital M and the capital A. The current radius is denoted by everything in between the capital A and the 0 0 1 of the x-axis rotation, large-arc flag and sweep flag. Finally the end location is given by all in between the 0 0 1 and the end of the string (denoted by a $ in regex).

So we save all the pieces in different variables and build/replace up the newArc using the final line in the if statement which has switched the start and end position.

The textPath section needs a small change. For the bottom half arcs, the dy attribute shouldn’t raise the labels above the arc paths, but lower the labels below the arc paths. So we need a small if statement which will result in two different dy values. (To be able to use the d.endAngle in the if statement I replaced the donutData by pie(donutData) in the .data() step. You can still reference the data itself by using d.data instead of just d, which you can see in the .text() line of code.)

//Append the label names on the outside
svg.selectAll(".donutText")
    .data(pie(donutData))
   .enter().append("text")
    .attr("class", "donutText")
    //Move the labels below the arcs for those slices with an end angle greater than 90 degrees
    .attr("dy", function(d,i) { return (d.endAngle > 90 * Math.PI/180 ? 18 : -11); })
   .append("textPath")
    .attr("startOffset","50%")
    .style("text-anchor","middle")
    .attr("xlink:href",function(d,i){return "#donutArc"+i;})
    .text(function(d){return d.data.name;});
like image 71
Roman Edirisinghe Avatar answered Sep 30 '22 12:09

Roman Edirisinghe


It looks like when d3 creates one of those filled arcs it actually creates a filled path shape that always(?) starts on the right and proceeds clockwise - even if you reverse startAngle and endAngle.

If you manually create your own arc path, and put your text on that, you can get it to do the right thing.

var cfg = {
    w:400,
    h:400
};

var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")

var arct = d3.svg.arc()
        .innerRadius(cfg.h / 5)
        .outerRadius(cfg.h / 3)
        .startAngle(Math.PI/2)
        .endAngle(Math.PI*1.5);

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

// Radius of line text sits on. A value of 3.5 makes it slightly closer to the
// outer radius (so text is placed in the middle of the blue line).
var textpathRadius = (cfg.h / 3.5);

// Make a path for the text to sit on that goes in an anti-clockwise direction.
var textpath = g.append("svg:path")
    .attr("id","zzz")
    .style("display","none")
    .attr("d", "M -"+textpathRadius+" 0 A "+textpathRadius+" "+textpathRadius+" 0 0 0 "+textpathRadius+" 0")
    .attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");

var text = g.append("text")
            .style("font-size",30)
            .style("fill","#F8F8F8")
            .attr("dy",0)
            .append("textPath")
            .attr("xlink:href","#zzz")
            .attr("startOffset","50%")
            .style("text-anchor","middle")
            .text("some text");

I've never used d3 before so there might be an easier or cleaner way to do what I've done. But at least it should give you a place to start.

Updated fiddle: http://jsfiddle.net/3DfVD/

like image 32
Paul LeBeau Avatar answered Sep 30 '22 13:09

Paul LeBeau