【问题标题】:D3 curved labels in the center of arc圆弧中心的 D3 弯曲标签
【发布时间】:2014-03-09 04:03:30
【问题描述】:

我已经能够构建带标签的甜甜圈图,就像在下面的小提琴中一样:

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

但现在我试图将标签放在每条弧线的中间并沿着弧线跨越它们(弯曲标签以跟随每条弧线)。为此,我一直在考虑使用 d3.svg.line.radial 函数将 svg:textsvg:textPath 放在一起。

然后我偶然发现了以下小提琴:

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

但是我很难将前小提琴的var arcs(具有实际数据的那个)与后小提琴的var line联系起来,因为后小提琴使用d3.range函数作为数据。

我已经试错了好几个小时,但没有任何效果。有谁知道d3.svg.line.radiald3.svg.arc 是如何协同工作的?

【问题讨论】:

    标签: javascript graph svg charts d3.js


    【解决方案1】:

    我想到了一种不同的方法。这是一种稍微迂回的方式,但需要很多 更少的自定义代码。

    不用创建自定义线插值器来绘制圆弧,而是使用 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')、逗号或空格,但不匹配任何其他表示不同类型路径命令的字母。我认为其余部分是不言自明的。

    【讨论】:

      【解决方案2】:

      d3.svg.line.radial 函数根据每个点的输入极坐标(半径和角度)在数组中的多个点之间构造一系列三次贝塞尔曲线(不是圆弧)。

      (您链接到的示例似乎画了一个圆,但这只是因为它将圆分成许多紧密间隔的点——尝试使用 5 个点而不是 50 个点,您会看到曲线的形状不是真正的圆形。)

      d3.svg.arc 函数根据 innerRadius、outerRadius、startAngle 和 endAngle 的值构造一个由两个同心圆弧和连接它们的直线组成的形状。

      这两种方法都指定从“12 点钟”开始的弧度角(垂直向上)。但是,要使径向线函数与弧数据对象一起工作存在一些困难。

      第一个问题是线生成器期望传递一个由多个点组成的数组,而不是单个对象。为了解决这个问题,您必须将路径元素的基准设置为弧组对象的数组,重复两次,一次用于弧的开始,一次用于弧的结束,然后在i判断每个点的角度值是用startAngle还是endAngle。

      这是创建这些路径的小提琴的变体。我没有费心让文本沿着路径运行,我只是用黑色绘制路径:
      http://jsfiddle.net/MX7JC/688/

      现在你看到了第二个问题:如果只给定两个点,线生成器只会在它们之间创建一条直线。

      查看简单曲线示例:http://jsfiddle.net/4VnHn/5/

      为了使用默认线生成器获得任何类型的曲线,您需要添加额外的点作为控制点,并更改the line interpolate method to an "open" option 以便不绘制结束控制点。我发现将起点和终点控制点设置为超出曲线起点和终点 45 度(围绕圆)创建的曲线与我的简单示例中的圆弧相似。

      查看更好的简单曲线示例:http://jsfiddle.net/4VnHn/6/

      为了您的可视化,曲线生成器现在必须通过数组中重复 四次 次的数据对象传递,角度访问器现在需要一个 switch 语句来找出不同的点:http://jsfiddle.net/MX7JC/689/

      结果对于小甜甜圈段是可以接受的,但对于那些本身宽度超过 45 度的甜甜圈段来说是可以接受的 - 在这些情况下,控制点最终会在圆周围很远,以至于它们完全偏离曲线.曲线生成器对圆一无所知,它只是试图平滑连接点以显示从一个到下一个的趋势。

      更好的解决方案是实际绘制弧,使用arc notation for SVG paths。弧生成器使用弧表示法,但它创建完整的二维形状。要使用线生成器创建弧线,您将需要一个自定义线插值器函数,然后您可以将其传递给线生成器的 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”的内容);
      • 为每个标签添加&lt;textPath&gt; element
      • 将文本路径的 xlink:href 属性设置为 # 加上该数据的相同唯一 ID 值

      【讨论】:

        猜你喜欢
        • 2012-05-10
        • 1970-01-01
        • 1970-01-01
        • 2017-03-19
        • 2018-10-28
        • 2018-01-25
        • 1970-01-01
        • 2014-01-09
        • 2015-08-06
        相关资源
        最近更新 更多