如何使d3路径描边向内弯曲?就像 "cardinal-closed" 插值的反向

How to make d3 path stroke to curve inside? Like the reverse of "cardinal-closed" interpolation

我正在使用 d3 开发一个新项目,创建一个显示从 0 到 10 的分数的图表。数据如下所示:

var data = [
    {axis: 'People', value: getRandomScore()}, 
    {axis: 'Leadership', value: getRandomScore()}, 
    {axis: 'Resources', value: getRandomScore()},
    {axis: 'Processes', value: getRandomScore()},
    {axis: 'Strategy', value: getRandomScore()},
    {axis: 'Culture', value: getRandomScore()},
];

getRandomScore 是一种 returns 从 0 到 10 的随机整数的方法。 这是我设法实现的结果:

这是艺术品:

问题是 d3 没有选项可以让路径描边(通过点)向内弯曲,使路径看起来像艺术作品中的星形,我需要一些帮助来创建一个函数会在曲线内做那件事。

你没有具体说明你是如何绘制的,所以在我的回答中我会假设它是使用 d3.svg.line.radial 的极坐标图的变体。获得所需外观的最简单方法是在现有点之间插入 "fake" 点以强制向内插值。

假设你的点位于圆极轴上(x 以弧度为单位):

var data = [
  [Math.PI / 3, someValue],
  [0 * Math.PI, someValue],
  [(5 * Math.PI / 3), someValue],
  [(4 * Math.PI / 3), someValue],
  [Math.PI, someValue],
  [(2 * Math.PI) / 3, someValue]
];

然后使用这些弧度的中点:

var midPoints = [Math.PI / 6, (11 * Math.PI) / 6, 
  (3 * Math.PI) / 2, (7 * Math.PI) / 6, 
  (5 * Math.PI) / 6, Math.PI / 2];    
pD = [];
midPoints.forEach(function(d,i){
  var i2 = (i === 5) ? 0 : i + 1;
  var midY = d3.min([data[i][1],data[i2][1]]) / 2; // find the min of the two neighboring points and the "inner" fake to half the min
  pD.push(data[i]);
  pD.push([d, midY]);
});

我将我的内部假点设置为两个相邻点的最小值的一半。您可以调整它以获得您想要的效果。

完整的工作代码如下:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .frame {
    fill: none;
    stroke: #000;
  }
  
  .axis text {
    font: 10px sans-serif;
  }
  
  .axis line,
  .axis circle {
    fill: none;
    stroke: steelblue;
    stroke-dasharray: 4;
  }
  
  .axis:last-of-type circle {
    stroke: steelblue;
    stroke-dasharray: none;
  }
  
  .line {
    fill: none;
    stroke: orange;
    stroke-width: 3px;
  }
</style>

<body>
  <script src="//d3js.org/d3.v3.min.js"></script>
  <script>
    var width = 500,
      height = 500,
      radius = Math.min(width, height) / 2 - 30;

    var r = d3.scale.linear()
      .domain([0, 2])
      .range([0, radius]);

    var line = d3.svg.line.radial()
      .radius(function(d) {
        return r(d[1]);
      })
      .angle(function(d) {
        return -d[0] + Math.PI / 2;
      });

    var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

    var gr = svg.append("g")
      .attr("class", "r axis")
      .selectAll("g")
      .data(r.ticks(3).slice(1))
      .enter().append("g");

    gr.append("circle")
      .attr("r", r);

    var ga = svg.append("g")
      .attr("class", "a axis")
      .selectAll("g")
      .data(d3.range(0, 360, 60))
      .enter().append("g")
      .attr("transform", function(d) {
        return "rotate(" + -d + ")";
      });

    ga.append("line")
      .attr("x2", radius);

    var line = d3.svg.line.radial()
      .radius(function(d) {
        return r(d[1]);
      })
      .angle(function(d) {
        return -d[0] + Math.PI / 2;
      })
      .interpolate("cardinal-closed");

    var data = [
      [Math.PI / 3, Math.random() + 1],
      [0 * Math.PI, Math.random() + 1],
      [(5 * Math.PI / 3), Math.random() + 1],
      [(4 * Math.PI / 3), Math.random() + 1],
      [Math.PI, Math.random() + 1],
      [(2 * Math.PI) / 3, Math.random() + 1]
    ]

    var midPoints = [Math.PI / 6, (11 * Math.PI) / 6, 
      (3 * Math.PI) / 2, (7 * Math.PI) / 6, 
      (5 * Math.PI) / 6, Math.PI / 2];
    
    pD = [];
    midPoints.forEach(function(d,i){
      var i2 = (i === 5) ? 0 : i + 1;
      var midY = d3.min([data[i][1],data[i2][1]]) / 2;
      pD.push(data[i]);
      pD.push([d, midY]);
    });

    svg.selectAll("point")
      .data(data)
      .enter()
      .append("circle")
      .attr("class", "point")
      .attr("transform", function(d) {
        var coors = line([d]).slice(1).slice(0, -1);
        return "translate(" + coors + ")"
      })
      .attr("r", 8)
      .attr("fill", "steelblue");

    svg.append("path")
      .datum(pD)
      .attr("class", "line")
      .attr("d", line);
  </script>