在D3

时间:2015-05-20 17:40:34

标签: javascript svg d3.js arc-diagram

我正在创建一个弧形图,我希望能够找到一种方法来防止弧的重叠。有一个working bl.ock here的例子。

Arc diagram

在这种情况下,较暗的线是重叠的线,其中多个节点共享相同的边。我想通过两次传递来防止这种情况发生:第一次将弧线交替到节点上方而不是下方,给出一种螺旋形状;如果在上方/下方已存在弧以帮助区分链接,则第二个将绘制稍大的弧。

var width   = 1000,
    height  = 500,
    margin  = 20,
    pad     = margin / 2,
    radius  = 6,
    yfixed  = pad + radius;

var color = d3.scale.category10();

// Main
//-----------------------------------------------------

function arcDiagram(graph) {
  var radius = d3.scale.sqrt()
    .domain([0, 20])
    .range([0, 15]);

  var svg = d3.select("#chart").append("svg")
      .attr("id", "arc")
      .attr("width", width)
      .attr("height", height);

  // create plot within svg
  var plot = svg.append("g")
    .attr("id", "plot")
    .attr("transform", "translate(" + pad + ", " + pad + ")");

  // fix graph links to map to objects
  graph.links.forEach(function(d,i) {
    d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
    d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
  });

  linearLayout(graph.nodes);
  drawLinks(graph.links);
  drawNodes(graph.nodes);
}

// layout nodes linearly
function linearLayout(nodes) {
  nodes.sort(function(a,b) {
    return a.uniq - b.uniq;
  })

  var xscale = d3.scale.linear()
    .domain([0, nodes.length - 1])
    .range([radius, width - margin - radius]);

  nodes.forEach(function(d, i) {
    d.x = xscale(i);
    d.y = yfixed;
  });
}

function drawNodes(nodes) {

  var gnodes = d3.select("#plot").selectAll("g.node")
    .data(nodes)
  .enter().append('g');

  var nodes = gnodes.append("circle")
    .attr("class", "node")
    .attr("id", function(d, i) { return d.name; })
    .attr("cx", function(d, i) { return d.x; })
    .attr("cy", function(d, i) { return d.y; })
    .attr("r", 5)
    .style("stroke", function(d, i) { return color(d.gender); });

  nodes.append("text")
    .attr("dx", function(d) { return 20; })
    .attr("cy", ".35em")
    .text(function(d) { return d.name; })

}

function drawLinks(links) {
  var radians = d3.scale.linear()
  .range([Math.PI / 2, 3 * Math.PI / 2]);

  var arc = d3.svg.line.radial()
    .interpolate("basis")
    .tension(0)
    .angle(function(d) { return radians(d); });

  d3.select("#plot").selectAll(".link")
    .data(links)
  .enter().append("path")
    .attr("class", "link")
    .attr("transform", function(d,i) {
      var xshift = d.source.x + (d.target.x - d.source.x) / 2;
      var yshift = yfixed;
      return "translate(" + xshift + ", " + yshift + ")";
    })
    .attr("d", function(d,i) {
      var xdist = Math.abs(d.source.x - d.target.x);
      arc.radius(xdist / 2);
      var points = d3.range(0, Math.ceil(xdist / 3));
      radians.domain([0, points.length - 1]);
      return arc(points);
    });
}

关于如何开始解决问题的任何指示?

1 个答案:

答案 0 :(得分:1)

以下是bl.ock供参考。它以灰色显示原始路径,以红色显示建议路径。

首先存储给定路径发生次数的计数:

graph.links.forEach(function(d,i) {
  var pathCount = 0;
  for (var j = 0; j < i; j++) {
    var otherPath = graph.links[j];
    if (otherPath.source === d.source && otherPath.target === d.target) {
      pathCount++;
    }
  }

  d.pathCount = pathCount;
});

然后,一旦你有了这些数据,我会使用椭圆而不是径向线,因为看起来径向线只能绘制一条圆的曲线:

d3.select("#plot").selectAll(".ellipse-link")
  .data(links)
.enter().append("ellipse")
  .attr("fill", "transparent")
  .attr("stroke", "gray")
  .attr("stroke-width", 1)
  .attr("cx", function(d) {
    return (d.target.x - d.source.x) / 2 + radius;
  })
  .attr("cy", pad)
  .attr("rx", function(d) {
    return Math.abs(d.target.x - d.source.x) / 2;
  })
  .attr("ry", function(d) {
    return 150 + d.pathCount * 20;
  })
  .attr("transform", function(d,i) {
    var xshift = d.source.x - radius;
    var yshift = yfixed;
    return "translate(" + xshift + ", " + yshift + ")";
  });

请注意,更改上方ry的值将更改不同曲线的高度。

最后,您必须使用clippath来限制实际显示的每个椭圆的区域,以便它们仅显示在节点下方。 (这不是在bl.ock中完成的)