D3曲线标签位于弧形中心

3

我已经能够像下面的示例一样构建带标签的甜甜圈图表:

http://jsfiddle.net/MX7JC/9/

但现在我正在尝试将标签放置在每个弧的中间,并沿着弧进行跨越(使标签沿着每个弧线弯曲)。为了做到这一点,我一直在考虑使用d3.svg.line.radial函数将svg:text沿着svg:textPath放置。

然后我偶然发现了以下代码:

http://jsfiddle.net/Wexcode/CrDUy/

然而,我在将前一个fiddle中拥有实际数据的var arcs与后一个fiddle中使用d3.range函数作为数据的var line联系起来时遇到了困难。我已经试错了几个小时,但没有任何进展。有人知道如何让d3.svg.line.radiald3.svg.arc一起工作吗?
2个回答

9
d3.svg.line.radial函数基于输入的极坐标(半径和角度)构建多个点之间的一系列(而不是弧线)。 (您链接的示例似乎绘制了一个圆形,但这是因为它将圆形分解成许多紧密间隔的点 - 尝试使用5个点代替50个点,您会发现曲线的形状不是一个真正的圆形。) d3.svg.arc函数根据innerRadius、outerRadius、startAngle和endAngle的值构造由两个同心圆弧和连接它们的直线组成的形状。
两种方法都以从“12 o'clock”(向上垂直)开始的弧度指定角度。 但是,在将径向线函数与弧数据对象配合使用时存在几个困难。
第一个问题是,线生成器希望传递一个多个点的数组,而不是单个对象。为了解决这个问题,您必须将路径元素的数据设置为重复两次的圆弧组对象的数组,一次用于圆弧的起点,一次用于终点,然后在中使用函数来确定每个点的角度值应该使用startAngle还是endAngle。
这里是您fiddle的变体,创建这些路径。我没有费心让文本沿着路径运行,我只是用黑色绘制路径:http://jsfiddle.net/MX7JC/688/ 现在看到第二个问题:如果只给出两个点,则线生成器将只在它们之间创建一条直线。
请参见简单的曲线示例:http://jsfiddle.net/4VnHn/5/ 为了使用默认线生成器获得任何类型的曲线,您需要添加其他点以充当控制点,并将 line interpolate 方法更改为“open”选项,以便不绘制端点控制点。 我发现使开始和结束控制点在曲线的开始和结束点(围绕圆)的45度之外创建的曲线与我的简单示例中的弧接受度相似。
请看更好的简单曲线示例:http://jsfiddle.net/4VnHn/6/ 为了可视化,现在需要将数据对象重复四次放入数组中传递给曲线生成器,并且角度访问器现在需要一个 switch 语句来确定不同的点:http://jsfiddle.net/MX7JC/689/ 对于小甜甜圈片段的结果是可以接受的,但对于宽度超过 45 度的部分则不行。在这些情况下,控制点距离圆周太远,完全扰乱了曲线。曲线生成器并不知道任何关于圆的信息,它仅仅是试图平稳地连接点以显示从一个到下一个的趋势。
更好的解决方案是实际绘制一条弧,使用 SVG 路径的弧记号。弧生成器使用弧符号,但它创建整个二维形状。要使用线生成器创建弧,您需要一个自定义线插值器函数,然后将其传递给线生成器的 interpolate 方法。
线生成器将执行自定义线插值器函数,传递已从极坐标转换为 x、y 坐标的点数组。从那里,您需要定义弧方程。由于弧函数还需要知道圆弧半径,我使用了一个嵌套函数--外部函数接受半径作为参数并返回将接受点数组作为参数的函数:
function arcInterpolator(r) {
    //creates a line interpolator function
    //which will draw an arc of radius `r`
    //between successive polar coordinate points on the line

    return function(points) { 
    //the function must return a path definition string
    //that can be appended after a "M" command

        var allCommands = [];

        var startAngle; //save the angle of the previous point
                        //in order to allow comparisons to determine
                        //if this is large arc or not, clockwise or not

        points.forEach(function(point, i) { 

            //the points passed in by the line generator
            //will be two-element arrays of the form [x,y]
            //we also need to know the angle:        
            var angle = Math.atan2(point[0], point[1]);
            //console.log("from", startAngle, "to", angle);

            var command;

            if (i) command = ["A", //draw an arc from the previous point to this point
                        r, //x-radius
                        r, //y-radius (same as x-radius for a circular arc)
                        0, //angle of ellipse (not relevant for circular arc)
                        +(Math.abs(angle - startAngle) > Math.PI), 
                           //large arc flag,
                           //1 if the angle change is greater than 180degrees
                           // (pi radians),
                           //0 otherwise
                       +(angle < startAngle), //sweep flag, draws the arc clockwise
                       point[0], //x-coordinate of new point
                       point[1] //y-coordinate of new point
                       ];

            else command = point; //i = 0, first point of curve

            startAngle = angle;

            allCommands.push( command.join(" ") ); 
                //convert to a string and add to the command list
        });

        return allCommands.join(" ");
    };
}

实时示例:http://jsfiddle.net/4VnHn/8/

为了让它与您的甜甜圈图表配合使用,我从上面的版本开始生成直线,并更改了线条生成器的插值参数以使用我的自定义函数。我必须做的唯一额外更改是添加一个额外的检查,以确保图表上的任何角度都不超过360度(我确定这只是最后一个弧段的舍入问题,但导致我的函数将最终弧完全绕着圆形向后绘制):

var curveFunction = d3.svg.line.radial()
        .interpolate( arcInterpolator(r-45) )
        .tension(0)
        .radius(r-45)
        .angle(function(d, i) {
            return Math.min(
                i? d.endAngle : d.startAngle,
                Math.PI*2
                );
        //if i is 1 (true), this is the end of the curve,
        //if i is 0 (false), this is the start of the curve
        });

实时示例:http://jsfiddle.net/MX7JC/690/

最后,要将这些曲线用作文本路径:

  • 将曲线设置为无描边和无填充;
  • 为每个曲线赋予一个基于您的数据类别的唯一id值
    (对于您的示例,可以使用甜甜圈标签加上数据标签来得到类似于“textcurve-Agg-Intl”的东西);
  • 为每个标签添加<textPath>元素
  • 将文本路径的xlink:href属性设置为#加上相同的数据的唯一id值。

4
我想到了一种不同的方法。这是一个略微绕路的方法,但需要更少的自定义代码。
不要创建自定义行内插值器来绘制弧线,而是使用d3弧形生成器为整个饼图段创建曲线定义,然后使用正则表达式从饼图外部曲线中提取曲线定义。
简化示例:http://jsfiddle.net/4VnHn/10/ 带甜甜圈图表的示例:http://jsfiddle.net/MX7JC/691/ 关键代码:
var textArc = d3.svg.arc().outerRadius(r-45); //to generate the arcs for the text

textCurves.attr("d",  function(d) {
    var pie = textArc(d); //get the path code for the entire pie piece

    var justArc = /[Mm][\d\.\-e,\s]+[Aa][\d\.\-e,\s]+/; 
        //regex that matches a move statement followed by an arc statement

    return justArc.exec(pie)[0]; 
        //execute regular expression and extract matched part of string
});

r-45指的是甜甜圈内半径和外半径的中间位置。正则表达式中的[\d\.\-e,\s]+部分匹配数字、句点、负号、指数符号('e')、逗号或空格,但不匹配任何其他表示不同类型路径命令的字母。我认为其余部分都很容易理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接