【发布时间】:2017-02-12 00:35:25
【问题描述】:
我正在尝试绘制一个力导向图,每个节点有多个边,其中边看起来彼此不同,无论目标节点和源节点之间的距离如何。
tl;博士
当前图表:
- 我有一个力有向图
- 每个节点可以有多条边指向另一个节点
- 为了说明从一个节点发出的多条边,我使用弧而不是线来分隔每条(通过给出递增的
linknum并使用它来计算弧半径)
问题:
如果 2 个连接节点之间的距离变得太大,则弧会相互合并 - 因为弧不能“扩展”超过与 2 重叠的假想圆的半径。
因此,看起来应该彼此不同的边缘合并成一个大弧。
可能的解决方案:
我可以将 Arc 半径固定为对应于有效/合适的链接距离,因此这永远不会发生,但不幸的是,节点可以由用户拖动/锚定(为简洁起见,我在下面的代码示例中省略了用户拖动/锚定代码)
我认为使用贝塞尔曲线而不是没有扩展限制的弧线会更有意义。但是,我不确定在这种情况下我将如何着手计算他们的控制点
代码
拖动滑块会随着链接/边缘距离的增加重新绘制图表。高于 75 的值会创建我正在谈论的弧/链接/边缘合并。
function draw(linkDistance) {
var links = [{
source: "Microsoft",
target: "Amazon",
type: "licensing"
}, {
source: "Microsoft",
target: "Amazon",
type: "suit"
}, {
source: "Microsoft",
target: "Amazon",
type: "resolved"
}];
//sort links by source, then target
links.sort(function(a, b) {
if (a.source > b.source) {
return 1;
}
else if (a.source < b.source) {
return -1;
}
else {
if (a.target > b.target) {
return 1;
}
if (a.target < b.target) {
return -1;
}
else {
return 0;
}
}
});
//any links with duplicate source and target get an incremented 'linknum'
for (var i = 0; i < links.length; i++) {
if (i != 0 &&
links[i].source == links[i - 1].source &&
links[i].target == links[i - 1].target) {
links[i].linknum = links[i - 1].linknum + 1;
}
else {
links[i].linknum = 1;
};
};
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {
name: link.source
});
link.target = nodes[link.target] || (nodes[link.target] = {
name: link.target
});
});
var w = 300,
h = 200;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([w, h])
.linkDistance(linkDistance)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
// Per-type markers, as they don't inherit styles.
svg.append("svg:defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) {
return "link " + d.type;
})
.attr("marker-end", function(d) {
return "url(#" + d.type + ")";
});
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) {
return d.name;
});
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = 75 / d.linknum; //linknum is defined above
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
}
window.onload = function() {
document.getElementById("range").addEventListener("input", function(e) {
var value = this.value;
document.getElementById("chart").innerHTML = "";
document.getElementById("label").innerHTML = value;
draw(value);
})
draw(70);
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
marker#licensing {
fill: green;
}
path.link.licensing {
stroke: green;
}
path.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.9/d3.min.js"></script>
<input type="range" id="range" step="2" value="70" min="50" max="150">
<span id="label"></span>
<div id="chart"></div>
【问题讨论】:
标签: javascript d3.js