我对 D3 很陌生,这就是我到目前为止所做的 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)"})
除了正在呈现的链接之外,这按预期工作。例如,此链接:
但是我希望该链接是:
我怎样才能在 d3 中实现这一点?
最佳答案
惯用的方法是使用 path
元素而不是 line
并使用 d3.svg.line()
创建链接。这样,箭头也可以工作,并且可以完全轻松地设置动画。
笔记
在使用这个(非常有趣!)示例时,我发现了一些系统性问题......
显然 MS 无法解决这个问题,但是在渲染带有标记的元素时存在问题。 work-arround因为这是在其父级上插入路径,这就是 force ontick 事件处理程序中这一行的目的......
link.each(function() {this.parentNode.insertBefore(this, this); });
在示例中,我们有这样的路径元素指令
d="M28,46L28,23L77,23"
渲染两条正交线。这适用于过滤器并且投影按预期呈现,但是,当节点被拖动使得其中一条线的长度短于标记的相应尺寸时,出现了一个问题:路径元素,包括标记,开始被过滤器剪裁。我不明白到底发生了什么,但似乎过滤器的边界框(路径边界框的百分比)折叠到零高度,这以某种方式剪辑了引用路径元素。一旦路径边界框变为零,问题就会消失(至少在 Chrome 和 Opera 中是这样......)。
作为我尝试管理上述问题的一部分,我尝试将路径元素中的所有数字限制为整数,这是通过使用此代码向节点数据添加量化器 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])
}
});
})
});
这将创建一个
q
每个节点数据上的对象,对于任何返回数值的成员都有一个 getter - 我不需要考虑哪些成员,所以我只是将它们全部击中 - 这允许我这样做,例如...... node.attr("transform", function (d) {
return "translate(" + d.q.x + "," + d.q.y + ")";
})
所以,
d.q.x
和 d.q.y
是 d.x
的四舍五入版本和 d.y
.我打算在
linkPath
中使用它也可以使路径中的所有数字d
属性整数,但我意识到使用自定义 x
可以更好地实现这一点和 y
d3.svg.line()
中的访问器对象在这里... 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]]);
}
d3.svg.line().interpolate("linear")
返回的函数接受 [[p1x, p1y], [p2x, p2y], ... ]
形式的点数组并使用提供的标准插值器为路径 d
构造字符串值属性,(尝试其他标准 d3 插值器功能也很有趣,例如基础)。通过添加自定义访问器,确保提供的所有坐标都四舍五入到最接近的整数值。函数
linkPath
,在 force tick 回调中调用,简单地根据链接数据构造一个由三个点组成的数组,并将该数组传递给 connector
函数并返回一个可以用作d
的字符串path
的属性元素。调用签名确保它被传递给每个元素的绑定(bind)数据的副本......link.attr("d", linkPath);
因此,绑定(bind)到每个链接的数据用于创建三个点,这些点被插值并渲染为路径。
工作代码
需要管理一些问题以确保连接器和箭头正常工作,但这些问题在这里并不真正相关,所以我没有用修复来混淆代码......
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>
关于javascript - 如何在 d3 力有向图中将链接渲染为弯头连接器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31162488/