Ale*_*ail 5 d3.js force-layout
我正在寻找一种方法来将组插入到我的力导向图形可视化中.到目前为止,我找到了三个相关的例子:
我最喜欢的是从第一个链接添加非常接近结构的东西的简单方法,但没有太多开销.
现在我有一个非常标准的设置:
var link = g.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style(...
var node = g.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("id", function(d) { return d.id; })
Run Code Online (Sandbox Code Playgroud)
我希望只是从cola.js中获取d3代码并弄乱它,但是这个库看起来相当复杂,所以它不会太容易.我希望在d3中得到类似的东西并不太难:
谢谢!
And*_*eid 11
我正在按照标题"可视化节点组"的标题而不是建议的图片,但我认为在图像中调整显示边界框的答案并不难.
可能只有几个d3解决方案,所有这些解决方案几乎肯定都需要手动调整节点位置以保持节点正确分组.最终结果不一定是强制布局的典型结果,因为必须操纵链接和节点位置以显示除连接之外的分组 - 通常,最终结果将是每个力 - 节点电荷,长度强度和长度之间的折衷和小组.
实现目标的最简单方法可能是:
对于我在这里的例子,我将使用Mike的规范力布局.
使用链接的示例,当链接目标和链接源具有不同的组时,我们可以抑制链接强度.根据力布局的性质,可能需要改变指定的强度 - 更多相互连接的组可能需要具有较弱的组间链接强度.
要根据我们是否有组间链接来更改链接强度,我们可能会使用:
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(function(link) {
if (link.source.group == link.source.target) {
return 1; // stronger link for links within a group
}
else {
return 0.1; // weaker links for links across groups
}
}) )
.force("charge", d3.forceManyBody().strength(-20))
.force("center", d3.forceCenter(width / 2, height / 2));
Run Code Online (Sandbox Code Playgroud)
我们想要将组节点强制在一起,为此我们需要知道组的质心.数据结构simulation.nodes()不是最适合计算质心的,所以我们需要做一些工作:
var nodes = this.nodes();
var coords ={};
var groups = [];
// sort the nodes into groups:
node.each(function(d) {
if (groups.indexOf(d.group) == -1 ) {
groups.push(d.group);
coords[d.group] = [];
}
coords[d.group].push({x:d.x,y:d.y});
})
// get the centroid of each group:
var centroids = {};
for (var group in coords) {
var groupNodes = coords[group];
var n = groupNodes.length;
var cx = 0;
var tx = 0;
var cy = 0;
var ty = 0;
groupNodes.forEach(function(d) {
tx += d.x;
ty += d.y;
})
cx = tx/n;
cy = ty/n;
centroids[group] = {x: cx, y: cy}
}
Run Code Online (Sandbox Code Playgroud)
我们不需要调整每个节点 - 只是那些偏离它们质心的那些节点.对于那些距离足够远的人,我们可以使用质心的加权平均值和节点的当前位置来推近它们.
我修改用于确定在可视化冷却时是否应调整节点的最小距离.在可视化处于活动状态的大部分时间,当alpha为高时,优先级是分组,因此大多数节点将被强制朝向分组质心.当alpha降为零时,节点应该已经分组,强制其位置的需要不那么重要:
// don't modify points close the the group centroid:
var minDistance = 10;
// modify the min distance as the force cools:
if (alpha < 0.1) {
minDistance = 10 + (1000 * (0.1-alpha))
}
// adjust each point if needed towards group centroid:
node.each(function(d) {
var cx = centroids[d.group].x;
var cy = centroids[d.group].y;
var x = d.x;
var y = d.y;
var dx = cx - x;
var dy = cy - y;
var r = Math.sqrt(dx*dx+dy*dy)
if (r>minDistance) {
d.x = x * 0.9 + cx * 0.1;
d.y = y * 0.9 + cy * 0.1;
}
})
Run Code Online (Sandbox Code Playgroud)
这允许最简单的节点分组 - 它确保组shell之间没有重叠.我没有内置任何验证来确保节点或节点集不与其他组隔离 - 取决于可能的可视化复杂性.
我最初的想法是使用隐藏的画布来计算壳是否重叠,但是使用Voronoi,您可以计算每个组是否使用相邻单元进行合并.如果是非合并组,您可以在离散节点上使用更强大的强制.
要应用voronoi非常简单:
// append voronoi
var cells = svg.selectAll()
.data(simulation.nodes())
.enter().append("g")
.attr("fill",function(d) { return color(d.group); })
.attr("class",function(d) { return d.group })
var cell = cells.append("path")
.data(voronoi.polygons(simulation.nodes()))
Run Code Online (Sandbox Code Playgroud)
并在每个刻度上更新:
// update voronoi:
cell = cell.data(voronoi.polygons(simulation.nodes())).attr("d", renderCell);
Run Code Online (Sandbox Code Playgroud)
总而言之,在分组阶段这看起来像这样:
随着可视化最终停止:
如果第一张图像更可取,则在minDistanceα冷却下来时删除更改的部分.
这是使用上述方法的块.
我们可以使用另一个力图来定位每个组的理想质心,而不是使用每个组节点的质心.该力图将具有每个组的节点,每个组之间的链路强度将对应于组的节点之间的te个链路.使用此力图,我们可以将原始节点强制转向我们理想化的质心 - 第二力布局的节点.
这种方法在某些情况下可能具有优势,例如通过更大量地分组.这种方法可能会给你这样的东西:
我在这里添加了一个示例,但希望代码的注释能够充分理解,而不会像上面的代码那样细分.
阻止第二个例子.
voronoi很容易,但并不总是最美观的,您可以使用剪辑路径将多边形剪切为某种椭圆形,或使用渐变叠加层在多边形到达边缘时淡出多边形.根据图复杂性可能的一种选择是使用最小凸多边形,但这对于节点少于三个的组不起作用.在大多数情况下,边界框可能不起作用,除非您确实将强制因子保持在高水平(例如:minDistance在整个时间内保持非常低).权衡将永远是你想要展示的更多:连接或分组.