D3.js 的嵌套数据

Nested data with D3.js

我正在尝试使用父数据和嵌套数据生成 SVG 元素,但在显示内容时遇到了问题。这与 Combining Parent and Nested Data with d3.js 非常相似,后者非常有帮助,但没有完全解决我的问题。

对于这个例子,我正在绘制由多个点组成的 SVG 路径。我想为每个路径的线段绘制一条切线。它主要工作,但我无法为切线路径创建组,而是它们最终位于 SVG 的根部。我希望结构看起来像这样:

<svg width="100" height="100">

  <g id="0" class="linegroup">
    <g class="linepath">
      <path d="M0,0L10,10L20,20"></path>
    </g>
    <g class="tangentgroup">
      <path class="tan" d="M5,5L-5,15"></path>
      <path class="tan" d="M15,15L5,25"></path>
    </g>
  </g>

  <g id="1" class="linegroup">
    <g class="linepath">
      <path d="M30,30L40,40L50,50"></path>
    </g>
    <g class="tangentgroup">
      <path class="tan" d="M35,35L25,45"></path>
      <path class="tan" d="M45,45L35,55"></path>
    </g>
  </g>

</svg>

但是,所有切线路径都被附加到 svg,我无法创建 "tangentgroup" SVG 组:

<svg width="100" height="100">

  <g id="0" class="linegroup">
    <g class="linepath">
      <path d="M0,0L10,10L20,20"></path>
    </g>
  </g>

  <g id="1" class="linegroup">
    <g class="linepath">
      <path d="M30,30L40,40L50,50"></path>
    </g>
  </g>

  <path class="tan" d="M5,5L-5,15"></path>
  <path class="tan" d="M15,15L5,25"></path>
  <path class="tan" d="M35,35L25,45"></path>
  <path class="tan" d="M45,45L35,55"></path>

</svg>

这是我目前的代码。我也乐于接受有关如何以其他方式改进它的建议。

// test data
var lines = [{
      id: 0,
      coordinates: [[0,0],[10,10],[20,20]]},
    {
      id: 1,
      coordinates: [[30,30],[40,40],[50,50]]
    }];

var diameter = 100;

var d3line = d3.svg.line();

var svg = d3.select("body").append("svg")
    .attr("width", diameter)
    .attr("height", diameter);

// create groups for each line and its tangents
var linegroups = svg.selectAll('.linegroup')
    .data(lines)
  .enter().append('g')
    .attr('class', 'linegroup')
    .attr('id', function(d) { return d.id; });

// add the line path
linegroups.append('g')
  .attr('class', 'linepath')
.append('path')
  .attr("d", function(d) { return d3line(d.coordinates); });

/////// Problem section ////////

// create a group for the line's segment's tangents,
//  and create a tangent path for each segment
linegroups.each(function(line, i) {
  d3.selectAll(this)
      // The "tangentgroup" groups never show up
  .append('g')
    .attr('class', 'tangentgroup')
  .data(lineSegments(line.coordinates))
      // 'tan' paths get appended to parent svg, not to 'linegroup'.
  .enter().append('path')
    .attr('class', 'tan')
    .attr('d', function (d) {
      return d3line(tangentFromMidpoint(d));
    });
});

////////////////////////////////

// returns lineSegments, comprised of pairs of points
function lineSegments (coordinates) {
  return d3.range(coordinates.length - 1).map(function(i) {
    return [coordinates[i], coordinates[i + 1]];
  });
}

// returns a tangent line starting from the mid-point of the original line
function tangentFromMidpoint (line) {
  var p1 = line[0];
  var p2 = line[1];
  var midPoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
  var tv = tangentVectors(p1,p2)[0];
  return [midPoint, [tv[0] + midPoint[0], tv[1] + midPoint[1]]];
}

// Returns both tangent vectors (not unit-vector) for a line
function tangentVectors (p1,p2) {
  // if we define dx=x2-x1 and dy=y2-y1,
  //  then the normals are (-dy, dx) and (dy, -dx)
  var dx = p2[0] - p1[0];
  var dy = p2[1] - p1[1];
  return [[-dy, dx, dy, -dx]];
}

问题是您首先在没有 .selectAll() 的情况下调用 .data(),然后对 .enter() 选择进行操作。代码应该是

linegroups.each(function(line, i) {
  d3.select(this)
  .append('g')
    .attr('class', 'tangentgroup')
  .selectAll("path")
  .data(lineSegments(line.coordinates))
  .enter().append('path')
    .attr('class', 'tan')
    .attr('d', function (d) {
      return d3line(tangentFromMidpoint(d));
    });
});

完成演示 here。不过你甚至不需要 .each():

linegroups.append('g')
   .attr('class', 'tangentgroup')
   .selectAll("path")
   .data(function(line) { return lineSegments(line.coordinates); })
   .enter().append('path')
   .attr('class', 'tan')
   .attr('d', function (d) {
     return d3line(tangentFromMidpoint(d));
   });

完成演示 here