如何在d3力有向图中呈现弯头连接

时间:2023-02-02 21:15:53

I'm very new to D3 and this is what I have done till now here.

我对D3很陌生,这是我到现在为止所做的。

The actual code is over here:

实际的代码在这里

var width = 1840,
    height = 1480,
    constant = 100, 
    color = "#BCD8CD"

var nodes = [
    {label: '1st stage', x:   constant, y: 215 , width:70,height:50 , color :color , stage: true },
    {label: '2nd stage', x: constant + 150 , y: 215 ,width:70,height:50 ,color :color, stage: true },
    {label: '3rd stage', x: constant + 279, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '4th stage', x: constant + 460, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '5th stage', x: constant + 660, y: 215 ,width:70,height:50 ,color :color, stage: false },
    {label: '6th stage', x: constant + 350, y: 350 ,width:70,height:50, color :color, stage: true }
];

var links = [
    { source: 0, target: 1 },
    { source: 1, target: 2},
    { source: 2, target: 3},
    { source: 3, target: 4},
    { source: 1, target: 5}
];

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

var marker = svg.append('marker')
    .attr('id',"triangle")
    .attr('viewBox',"0 0 10 10")
    .attr('refX',"0")
    .attr('refY',"5")
    .attr('markerUnits','strokeWidth')
    .attr('markerWidth','4')
    .attr('markerHeight','3')
    .attr('orient','auto')

var path = marker.append('path')
      .attr('d',"M 0 0 L 10 5 L 0 10 z")

var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes)
    .links(links);
force.linkDistance(width/4);

var link = svg.selectAll('.link')
    .data(links)
    .enter().append('line')
    .attr("stroke-width", "2")
    .attr('marker-end','url(#triangle)')
    .attr('stroke','black')

var defs = svg.append("defs");

// create filter with id #drop-shadow
// height=130% so that the shadow is not clipped
var filter = defs.append("filter")
    .attr("id", "drop-shadow")
    .attr("height", "130%");

// SourceAlpha refers to opacity of graphic that this filter will be applied to
// convolve that with a Gaussian with standard deviation 3 and store result
// in blur
filter.append("feGaussianBlur")
    .attr("in", "SourceAlpha")
    .attr("stdDeviation", 3)
    .attr("result", "blur");

// translate output of Gaussian blur to the right and downwards with 2px
// store result in offsetBlur
var feOffset = filter.append("feOffset")
    .attr("in", "blur")
    .attr("dx", 2)
    .attr("dy", 2)
    .attr("result", "offsetBlur");

// overlay original SourceGraphic over translated blurred opacity by using
// feMerge filter. Order of specifying inputs is important!
var feMerge = filter.append("feMerge");

feMerge.append("feMergeNode")
    .attr("in", "offsetBlur")
feMerge.append("feMergeNode")
    .attr("in", "SourceGraphic");

var node = svg.selectAll('.node')
    .data(nodes)
    .enter().append('g')
    .attr('class', 'node')
.attr("transform", function(d){
    return "translate("+d.x+","+d.y+")";
})

node.append("rect").attr("class", "nodeRect")
        .attr("rx", 6)
        .attr("ry", 6)
        .attr('width', function(d) { return d.width; })
        .attr('height', function(d) { return d.height; })
        .style("fill", function(d) { return d.color; })
        .transition()
        .duration(1000) // this is 1s
        .delay(1000) 
        .style("fill",function(d){if(d.stage) return "#FF9966"})
        .style("filter",function(d){if(d.stage) return "url(#drop-shadow)"})


node.append("text").style("text-anchor", "middle")
        .style("pointer-events", "none")
        .style("font-weight", 900)
        .attr("fill", "white")
        .style("stroke-width", "0.3px")
        .style("font-size", "16px")
        .attr("y", function (d){return d.height/2+6;})
        .attr("x", function (d){return d.width/2;})
        .text(function (d) {return d.label;})

force.start();

link.attr('x1', function(d) { return d.source.x + d.source.width/2; })
    .attr('y1', function(d) { return d.source.y + d.source.height/2; })
    .attr('x2', function(d) { return d.target.x + d.target.width/2; })
    .attr('y2', function(d) { return d.target.y + d.target.height/2; })
    .transition()
    .duration(1000) // this is 1s
    .delay(1000) 
    .style("filter",function(d){if(d.source.stage) return "url(#drop-shadow)"})

this works as expected, apart from the links that is getting rendered. For example, this link :

除了要渲染的链接之外,这和预期的一样。例如,这个链接:

如何在d3力有向图中呈现弯头连接

however I want that link to be :

但是我希望这个链接是:

如何在d3力有向图中呈现弯头连接

How can I achieve this in d3?

如何在d3中实现这一点?

2 个解决方案

#1


5  

The idiomatic way is to use a path element instead of a line and use d3.svg.line() to create the links. This way the arrows work also and it is completely and easily animatable.

惯用方法是使用路径元素而不是行,并使用d3.svg.line()来创建链接。这样,箭头也可以工作,它是完全和容易动画。


Notes

While working with this (very interesting!) example, I found a couple of systemic issues...

在处理这个(非常有趣!)例子时,我发现了一些系统问题……

  1. Bug in IE
    Apparently MS can't be bothered fixing this, but there is a problem with rendering elements with markers on them. The work-arround for this is to insert the path on its parent and that is the purpose of this line in the force ontick event handler...
    link.each(function() {this.parentNode.insertBefore(this, this); });
  2. 显然,IE中的Bug MS不太愿意去修复这个问题,但是渲染带有标记的元素是有问题的。解决这个问题的方法是在父进程中插入路径,这就是在force ontick事件处理器中插入这一行的目的。(){ this.parentNode link.each(函数。方法(这个);});
  3. Problem with filter clipping
    In the example we have path element instructions like this d="M28,46L28,23L77,23" to render two orthogonal lines. This works fine with the filter and the drop shadow is rendered as expected, but, as the node is dragged such that the length of one of the lines is shorter than the corresponding dimension of the marker, a problem emerges: the path element, including the marker, start to get clipped by the filter.
    I don't understand what's going on exactly, but it seems that the bounding box for the filter, which is a percentage of the path bounding box, collapses to zero height and this somehow clips the referencing path element. As soon as the path bounding box becomes zero, the problem disappears (at least it does in Chrome and Opera...).
  4. 在这个例子中,我们有路径元素指令,比如d=“M28,46L28,23L77,23”来呈现两条正交的线。这工作好过滤器和阴影呈现如预期的那样,但是,随着节点拖着这样的一线的长度小于相应的维度的标记,一个问题出现了:路径元素,包括标志,开始剪的过滤器。我不太清楚具体是怎么回事,但是过滤器的边界框,它是路径边界框的一个百分比,坍缩到零高度,它以某种方式剪辑了引用路径元素。当路径边界框变为0时,问题就消失了(至少在Chrome和Opera中是这样的)。

如何在d3力有向图中呈现弯头连接

As part of my attempts to manage the above problems, I tried to limit all of the numbers in the path elements to be integers and this was implemented by adding a quantiser getter to the node data with this code...

作为管理上述问题的一部分,我试图将路径元素中的所有数字限制为整数,这是通过向节点数据添加一个quantiser getter来实现的。

 force.nodes().forEach(function(d) {
    d.q = {};
    Object.keys(d).forEach(function (p) {
      if (!isNaN(d[p])) Object.defineProperty(d.q, p, {
        get: function () {
          return Math.round(d[p])
        }
      });
    })
  });

This creates a q object on each node datum with a getter for any member that returns a numeric value - I don't need to think about which ones, so I just hit them all - and that allows me to do this, for example...

这在每个节点数据上创建了一个q对象,对于任何返回数值的成员都有一个getter—我不需要考虑是哪个值,所以我只点击它们—这允许我这样做,例如……

  node.attr("transform", function (d) {
    return "translate(" + d.q.x + "," + d.q.y + ")";
  })  

So, d.q.x and d.q.y are rounded versions of d.x and d.y. I was intending to use this in the linkPath function as well to make all of the numbers in the path d attribute integers, but I realised that this was better achieved with custom x and y accessors in the d3.svg.line() object here...

所以,d.q。x和d.q。y是d的四舍五入形式。我还打算在linkPath函数中使用它来创建路径d属性整数中的所有数字,但是我意识到在d3.svg.line()对象中使用定制的x和y访问器可以更好地实现这一点……

  var connector = d3.svg.line().interpolate("linear")
    .x(function(d){return Math.round(d[0])})
    .y(function(d){return Math.round(d[1])});
  function linkPath(d){
    var  h1 = d.source.height, w1 = d.source.width, x1 = d.source.x + w1/2, y1 = d.source.y + h1/2,
      h2 = d.target.height, w2 = d.target.width, x2 = d.target.x  - markerW - 4, y2 = d.target.y + h2/2;
      return connector([[x1, y1], [x1, y2], [x2, y2]]);
  }  

The function returned by d3.svg.line().interpolate("linear") accepts an array of points in the form of [[p1x, p1y], [p2x, p2y], ... ] and uses the provided, standard interpolator to construct a string value for the path d attribute, (It's also fun to try other, standard d3 interpolator functions like basis for example). By adding the custom accessors, all of the coordinates provided are assured to be rounded to the nearest integer value.
The function linkPath, which is called in the force tick callback, simply constructs an array of three points based on the link data and passes that array to the connector function and returns a string that can be used as the d attribute of a path element. The call signature assures that it is passed a copy of the bound datum for each element...

由d3.svg.line(). introate ("linear")返回的函数接受一个以[[[p1x, p1y], [p2x, p2y]为形式的点数组,……并使用所提供的标准插补器为path d属性构造一个字符串值(尝试其他标准的d3插补函数也很有趣,例如basis)。通过添加自定义访问器,可以确保提供的所有坐标都被四舍五入到最接近的整数值。函数linkPath(在force tick回调中调用)只是基于链接数据构造一个由三个点组成的数组,并将该数组传递给connector函数,并返回一个字符串,该字符串可以用作path元素的d属性。调用签名确保为每个元素传递绑定数据的副本……

link.attr("d", linkPath);  

So, the data bound to each link is used to create three points, which are interpolated and rendered as a path.

因此,绑定到每个链接的数据被用来创建三个点,这些点被插入并呈现为一条路径。


Working code

There are a few issues that need to be managed to ensure the connectors and the arrowheads work properly but these are not really relevant here so I didn't clutter up the code with fixes...

有一些问题需要处理,以确保连接器和箭头工作正常,但这些不是真正相关的这里,所以我没有混乱的代码修复……

  var width = 600,
    height = 148,
    constant = 10,
    color = "#BCD8CD"

  var scale = .75, w = 70*scale, h = 50*scale,
    nodes = [
    {label: '1st stage', x:   constant, y: 20*scale , width:w,height:h , color :color , stage: true },
    {label: '2nd stage', x: constant + 150*scale , y: 20*scale ,width:w,height:h ,color :color, stage: true },
    {label: '3rd stage', x: constant + 279*scale, y: 20*scale ,width:w,height:h, color :color, stage: false },
    {label: '4th stage', x: constant + 460*scale, y: 20*scale ,width:w,height:h, color :color, stage: false },
    {label: '5th stage', x: constant + 660*scale, y: 20*scale ,width:w,height:h ,color :color, stage: false },
    {label: '6th stage', x: constant + 350*scale, y: 100*scale ,width:w,height:h, color :color, stage: true }
    ].map(function(d, i){return (d.fixed = (i != 5), d)});

  var links = [
    { source: 0, target: 1 },
    { source: 1, target: 2},
    { source: 2, target: 3},
    { source: 3, target: 4},
    { source: 1, target: 5}
  ];

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

  var markerW = 4, markerH = 3,
    marker = svg.append('marker')
    .attr('id',"triangle")
    .attr('viewBox',"0 0 10 10")
    .attr('refX',"0")
    .attr('refY',5)
    .attr('markerUnits','strokeWidth')
    .attr('markerWidth',markerW)
    .attr('markerHeight',markerH)
    .attr('orient','auto')

  var path = marker.append('path')
    .attr('d',"M 0 0 L 10 5 L 0 10 z")

  var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes)
    .links(links)
    .linkDistance(width/4)
    .on("tick", function(e){
      //hack to force IE to do it's job!
      link.each(function() {this.parentNode.insertBefore(this, this); });

      link.attr("d", linkPath);
      node.attr("transform", function (d) {
        return "translate(" + d.q.x + "," + d.q.y + ")";
      })
    });
    force.nodes().forEach(function(d) {
      d.q = {};
      Object.keys(d).forEach(function (p) {
        if (!isNaN(d[p])) Object.defineProperty(d.q, p, {
          get: function () {
            return Math.round(d[p])
          }
        });
      })
    });

  var connector = d3.svg.line().interpolate("linear")
    .x(function(d){return Math.round(d[0])})
    .y(function(d){return Math.round(d[1])});
  function linkPath(d){
    return connector([[d.source.x + d.source.width/2, d.source.y + d.source.height/2],
      [d.source.x + d.source.width/2, d.target.y + d.target.height/2],
      [d.target.x  - markerW - 4, d.target.y + d.target.height/2]]);
  }

  var link = svg.selectAll('.link')
    .data(links)
    .enter().append('path')
    .attr("stroke-width", "2")
    .attr('marker-end','url(#triangle)')
    .attr('stroke','black')
    .attr("fill", "none");

  var defs = svg.append("defs");

  // create filter with id #drop-shadow
  // height=130% so that the shadow is not clipped
  var filter = defs.append("filter")
    .attr("id", "drop-shadow")
    .attr({"height": "200%", "width": "200%", x: "-50%", y: "-50%"});

  // SourceAlpha refers to opacity of graphic that this filter will be applied to
  // convolve that with a Gaussian with standard deviation 3 and store result
  // in blur
  filter.append("feGaussianBlur")
    .attr("in", "SourceAlpha")
    .attr("stdDeviation", 3)
    .attr("result", "blur");

  // translate output of Gaussian blur to the right and downwards with 2px
  // store result in offsetBlur
  var feOffset = filter.append("feOffset")
    .attr("in", "blur")
    .attr("dx", 2)
    .attr("dy", 2)
    .attr("result", "offsetBlur");

  // overlay original SourceGraphic over translated blurred opacity by using
  // feMerge filter. Order of specifying inputs is important!
  var feMerge = filter.append("feMerge");

  feMerge.append("feMergeNode")
    .attr("in", "offsetBlur")
  feMerge.append("feMergeNode")
    .attr("in", "SourceGraphic");

  var node = svg.selectAll('.node')
    .data(nodes)
    .enter().append('g')
    .attr('class', 'node')
    .attr("transform", function(d){
      return "translate("+ d.q.x+","+ d.q.y+")";
    })
  .call(force.drag)

  node.append("rect").attr("class", "nodeRect")
    .attr("rx", 6)
    .attr("ry", 6)
    .attr('width', function(d) { return d.width; })
    .attr('height', function(d) { return d.height; })
    .style("fill", function(d) { return d.color; })
    .transition()
    .duration(1000) // this is 1s
    .delay(1000)
    .style("fill",function(d){if(d.stage) return "#FF9966"})
    .style("filter",function(d){if(d.stage) return "url(#drop-shadow)"})


  node.append("text").style("text-anchor", "middle")
    .style("pointer-events", "none")
    .style("font-weight", 900)
    .attr("fill", "white")
    .style("stroke-width", "0.3px")
    .style("font-size", 16*scale + "px")
    .attr("y", function (d){return d.height/2+6*scale;})
    .attr("x", function (d){return d.width/2;})
    .text(function (d) {return d.label;})

  force.start();

    link.attr("d", linkPath)
   .transition()
    .duration(1000) // this is 1s
    .delay(1000)
    .style("filter",function(d){if(d.source.stage) return "url(#drop-shadow)"});

  d3.select("svg").append("text").attr({"y": height - 20, fill: "black"}).text("drag me!")
svg { overflow: visible;}
.node {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 2px;
}
.link {
  stroke: #777;
  stroke-width: 2px;
}
g.hover {
  background-color: rgba(0, 0, 0, .5);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

#2


1  

I found a work-around solution by creating an empty label:

我通过创建一个空标签找到了一个解决方案:

Working demo

演示工作

var nodes = [
    {label: '1st stage', x:   constant, y: 215 , width:70,height:50 , color :color , stage: true },
    {label: '2nd stage', x: constant + 150 , y: 215 ,width:70,height:50 ,color :color, stage: true },
    {label: '3rd stage', x: constant + 279, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '4th stage', x: constant + 460, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '5th stage', x: constant + 660, y: 215 ,width:70,height:50 ,color :color, stage: false },
    {label: '', x: constant + 185, y: 370 ,width:0,height:0 ,color :color, stage: true },
    {label: '6th stage', x: constant + 350, y: 350 ,width:70,height:50, color :color, stage: true }
];

var links = [
    { source: 0, target: 1 },
    { source: 1, target: 2},
    { source: 2, target: 3},
    { source: 3, target: 4},
    { source: 1, target: 5},
    { source: 5, target: 6}
];

#1


5  

The idiomatic way is to use a path element instead of a line and use d3.svg.line() to create the links. This way the arrows work also and it is completely and easily animatable.

惯用方法是使用路径元素而不是行,并使用d3.svg.line()来创建链接。这样,箭头也可以工作,它是完全和容易动画。


Notes

While working with this (very interesting!) example, I found a couple of systemic issues...

在处理这个(非常有趣!)例子时,我发现了一些系统问题……

  1. Bug in IE
    Apparently MS can't be bothered fixing this, but there is a problem with rendering elements with markers on them. The work-arround for this is to insert the path on its parent and that is the purpose of this line in the force ontick event handler...
    link.each(function() {this.parentNode.insertBefore(this, this); });
  2. 显然,IE中的Bug MS不太愿意去修复这个问题,但是渲染带有标记的元素是有问题的。解决这个问题的方法是在父进程中插入路径,这就是在force ontick事件处理器中插入这一行的目的。(){ this.parentNode link.each(函数。方法(这个);});
  3. Problem with filter clipping
    In the example we have path element instructions like this d="M28,46L28,23L77,23" to render two orthogonal lines. This works fine with the filter and the drop shadow is rendered as expected, but, as the node is dragged such that the length of one of the lines is shorter than the corresponding dimension of the marker, a problem emerges: the path element, including the marker, start to get clipped by the filter.
    I don't understand what's going on exactly, but it seems that the bounding box for the filter, which is a percentage of the path bounding box, collapses to zero height and this somehow clips the referencing path element. As soon as the path bounding box becomes zero, the problem disappears (at least it does in Chrome and Opera...).
  4. 在这个例子中,我们有路径元素指令,比如d=“M28,46L28,23L77,23”来呈现两条正交的线。这工作好过滤器和阴影呈现如预期的那样,但是,随着节点拖着这样的一线的长度小于相应的维度的标记,一个问题出现了:路径元素,包括标志,开始剪的过滤器。我不太清楚具体是怎么回事,但是过滤器的边界框,它是路径边界框的一个百分比,坍缩到零高度,它以某种方式剪辑了引用路径元素。当路径边界框变为0时,问题就消失了(至少在Chrome和Opera中是这样的)。

如何在d3力有向图中呈现弯头连接

As part of my attempts to manage the above problems, I tried to limit all of the numbers in the path elements to be integers and this was implemented by adding a quantiser getter to the node data with this code...

作为管理上述问题的一部分,我试图将路径元素中的所有数字限制为整数,这是通过向节点数据添加一个quantiser getter来实现的。

 force.nodes().forEach(function(d) {
    d.q = {};
    Object.keys(d).forEach(function (p) {
      if (!isNaN(d[p])) Object.defineProperty(d.q, p, {
        get: function () {
          return Math.round(d[p])
        }
      });
    })
  });

This creates a q object on each node datum with a getter for any member that returns a numeric value - I don't need to think about which ones, so I just hit them all - and that allows me to do this, for example...

这在每个节点数据上创建了一个q对象,对于任何返回数值的成员都有一个getter—我不需要考虑是哪个值,所以我只点击它们—这允许我这样做,例如……

  node.attr("transform", function (d) {
    return "translate(" + d.q.x + "," + d.q.y + ")";
  })  

So, d.q.x and d.q.y are rounded versions of d.x and d.y. I was intending to use this in the linkPath function as well to make all of the numbers in the path d attribute integers, but I realised that this was better achieved with custom x and y accessors in the d3.svg.line() object here...

所以,d.q。x和d.q。y是d的四舍五入形式。我还打算在linkPath函数中使用它来创建路径d属性整数中的所有数字,但是我意识到在d3.svg.line()对象中使用定制的x和y访问器可以更好地实现这一点……

  var connector = d3.svg.line().interpolate("linear")
    .x(function(d){return Math.round(d[0])})
    .y(function(d){return Math.round(d[1])});
  function linkPath(d){
    var  h1 = d.source.height, w1 = d.source.width, x1 = d.source.x + w1/2, y1 = d.source.y + h1/2,
      h2 = d.target.height, w2 = d.target.width, x2 = d.target.x  - markerW - 4, y2 = d.target.y + h2/2;
      return connector([[x1, y1], [x1, y2], [x2, y2]]);
  }  

The function returned by d3.svg.line().interpolate("linear") accepts an array of points in the form of [[p1x, p1y], [p2x, p2y], ... ] and uses the provided, standard interpolator to construct a string value for the path d attribute, (It's also fun to try other, standard d3 interpolator functions like basis for example). By adding the custom accessors, all of the coordinates provided are assured to be rounded to the nearest integer value.
The function linkPath, which is called in the force tick callback, simply constructs an array of three points based on the link data and passes that array to the connector function and returns a string that can be used as the d attribute of a path element. The call signature assures that it is passed a copy of the bound datum for each element...

由d3.svg.line(). introate ("linear")返回的函数接受一个以[[[p1x, p1y], [p2x, p2y]为形式的点数组,……并使用所提供的标准插补器为path d属性构造一个字符串值(尝试其他标准的d3插补函数也很有趣,例如basis)。通过添加自定义访问器,可以确保提供的所有坐标都被四舍五入到最接近的整数值。函数linkPath(在force tick回调中调用)只是基于链接数据构造一个由三个点组成的数组,并将该数组传递给connector函数,并返回一个字符串,该字符串可以用作path元素的d属性。调用签名确保为每个元素传递绑定数据的副本……

link.attr("d", linkPath);  

So, the data bound to each link is used to create three points, which are interpolated and rendered as a path.

因此,绑定到每个链接的数据被用来创建三个点,这些点被插入并呈现为一条路径。


Working code

There are a few issues that need to be managed to ensure the connectors and the arrowheads work properly but these are not really relevant here so I didn't clutter up the code with fixes...

有一些问题需要处理,以确保连接器和箭头工作正常,但这些不是真正相关的这里,所以我没有混乱的代码修复……

  var width = 600,
    height = 148,
    constant = 10,
    color = "#BCD8CD"

  var scale = .75, w = 70*scale, h = 50*scale,
    nodes = [
    {label: '1st stage', x:   constant, y: 20*scale , width:w,height:h , color :color , stage: true },
    {label: '2nd stage', x: constant + 150*scale , y: 20*scale ,width:w,height:h ,color :color, stage: true },
    {label: '3rd stage', x: constant + 279*scale, y: 20*scale ,width:w,height:h, color :color, stage: false },
    {label: '4th stage', x: constant + 460*scale, y: 20*scale ,width:w,height:h, color :color, stage: false },
    {label: '5th stage', x: constant + 660*scale, y: 20*scale ,width:w,height:h ,color :color, stage: false },
    {label: '6th stage', x: constant + 350*scale, y: 100*scale ,width:w,height:h, color :color, stage: true }
    ].map(function(d, i){return (d.fixed = (i != 5), d)});

  var links = [
    { source: 0, target: 1 },
    { source: 1, target: 2},
    { source: 2, target: 3},
    { source: 3, target: 4},
    { source: 1, target: 5}
  ];

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

  var markerW = 4, markerH = 3,
    marker = svg.append('marker')
    .attr('id',"triangle")
    .attr('viewBox',"0 0 10 10")
    .attr('refX',"0")
    .attr('refY',5)
    .attr('markerUnits','strokeWidth')
    .attr('markerWidth',markerW)
    .attr('markerHeight',markerH)
    .attr('orient','auto')

  var path = marker.append('path')
    .attr('d',"M 0 0 L 10 5 L 0 10 z")

  var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes)
    .links(links)
    .linkDistance(width/4)
    .on("tick", function(e){
      //hack to force IE to do it's job!
      link.each(function() {this.parentNode.insertBefore(this, this); });

      link.attr("d", linkPath);
      node.attr("transform", function (d) {
        return "translate(" + d.q.x + "," + d.q.y + ")";
      })
    });
    force.nodes().forEach(function(d) {
      d.q = {};
      Object.keys(d).forEach(function (p) {
        if (!isNaN(d[p])) Object.defineProperty(d.q, p, {
          get: function () {
            return Math.round(d[p])
          }
        });
      })
    });

  var connector = d3.svg.line().interpolate("linear")
    .x(function(d){return Math.round(d[0])})
    .y(function(d){return Math.round(d[1])});
  function linkPath(d){
    return connector([[d.source.x + d.source.width/2, d.source.y + d.source.height/2],
      [d.source.x + d.source.width/2, d.target.y + d.target.height/2],
      [d.target.x  - markerW - 4, d.target.y + d.target.height/2]]);
  }

  var link = svg.selectAll('.link')
    .data(links)
    .enter().append('path')
    .attr("stroke-width", "2")
    .attr('marker-end','url(#triangle)')
    .attr('stroke','black')
    .attr("fill", "none");

  var defs = svg.append("defs");

  // create filter with id #drop-shadow
  // height=130% so that the shadow is not clipped
  var filter = defs.append("filter")
    .attr("id", "drop-shadow")
    .attr({"height": "200%", "width": "200%", x: "-50%", y: "-50%"});

  // SourceAlpha refers to opacity of graphic that this filter will be applied to
  // convolve that with a Gaussian with standard deviation 3 and store result
  // in blur
  filter.append("feGaussianBlur")
    .attr("in", "SourceAlpha")
    .attr("stdDeviation", 3)
    .attr("result", "blur");

  // translate output of Gaussian blur to the right and downwards with 2px
  // store result in offsetBlur
  var feOffset = filter.append("feOffset")
    .attr("in", "blur")
    .attr("dx", 2)
    .attr("dy", 2)
    .attr("result", "offsetBlur");

  // overlay original SourceGraphic over translated blurred opacity by using
  // feMerge filter. Order of specifying inputs is important!
  var feMerge = filter.append("feMerge");

  feMerge.append("feMergeNode")
    .attr("in", "offsetBlur")
  feMerge.append("feMergeNode")
    .attr("in", "SourceGraphic");

  var node = svg.selectAll('.node')
    .data(nodes)
    .enter().append('g')
    .attr('class', 'node')
    .attr("transform", function(d){
      return "translate("+ d.q.x+","+ d.q.y+")";
    })
  .call(force.drag)

  node.append("rect").attr("class", "nodeRect")
    .attr("rx", 6)
    .attr("ry", 6)
    .attr('width', function(d) { return d.width; })
    .attr('height', function(d) { return d.height; })
    .style("fill", function(d) { return d.color; })
    .transition()
    .duration(1000) // this is 1s
    .delay(1000)
    .style("fill",function(d){if(d.stage) return "#FF9966"})
    .style("filter",function(d){if(d.stage) return "url(#drop-shadow)"})


  node.append("text").style("text-anchor", "middle")
    .style("pointer-events", "none")
    .style("font-weight", 900)
    .attr("fill", "white")
    .style("stroke-width", "0.3px")
    .style("font-size", 16*scale + "px")
    .attr("y", function (d){return d.height/2+6*scale;})
    .attr("x", function (d){return d.width/2;})
    .text(function (d) {return d.label;})

  force.start();

    link.attr("d", linkPath)
   .transition()
    .duration(1000) // this is 1s
    .delay(1000)
    .style("filter",function(d){if(d.source.stage) return "url(#drop-shadow)"});

  d3.select("svg").append("text").attr({"y": height - 20, fill: "black"}).text("drag me!")
svg { overflow: visible;}
.node {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 2px;
}
.link {
  stroke: #777;
  stroke-width: 2px;
}
g.hover {
  background-color: rgba(0, 0, 0, .5);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

#2


1  

I found a work-around solution by creating an empty label:

我通过创建一个空标签找到了一个解决方案:

Working demo

演示工作

var nodes = [
    {label: '1st stage', x:   constant, y: 215 , width:70,height:50 , color :color , stage: true },
    {label: '2nd stage', x: constant + 150 , y: 215 ,width:70,height:50 ,color :color, stage: true },
    {label: '3rd stage', x: constant + 279, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '4th stage', x: constant + 460, y: 215 ,width:70,height:50, color :color, stage: false },
    {label: '5th stage', x: constant + 660, y: 215 ,width:70,height:50 ,color :color, stage: false },
    {label: '', x: constant + 185, y: 370 ,width:0,height:0 ,color :color, stage: true },
    {label: '6th stage', x: constant + 350, y: 350 ,width:70,height:50, color :color, stage: true }
];

var links = [
    { source: 0, target: 1 },
    { source: 1, target: 2},
    { source: 2, target: 3},
    { source: 3, target: 4},
    { source: 1, target: 5},
    { source: 5, target: 6}
];