我正在寻找一种方法来将群组插入到我的力导向图形可视化中。到目前为止,我找到了三个相关的例子:
Cola.js这需要添加另一个库,并可能改编我的代码以适应这个不同的库。
This block,这很难解开。
This slide,这不是我想要的,而是在正确的道路上......
我最喜欢的是从第一个链接添加非常接近结构的东西的简单方法,但没有太多开销。
现在我有一个非常标准的设置:
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; })
我希望从cola.js中获取d3代码并弄乱它,但是这个库看起来相当复杂,所以它不会太容易。我希望在d3中得到类似的东西并不太难:
谢谢!
答案 0 :(得分:10)
我跟随标题"可视化节点组"比建议的图片更多,但我认为在图像中调整显示边界框的答案并不困难
可能只有几个d3解决方案,所有这些解决方案几乎肯定都需要手动调整节点位置以保持节点正确分组。最终结果绝对不是典型的力布局,因为必须操纵链路和节点位置以显示除连接之外的分组 - 通常,最终结果将是每个力 - 节点电荷,长度强度之间的折衷和长度,和组。
实现目标的最简单方法可能是:
在我的例子中,我将使用Mike的规范force layout。
使用链接的示例,当链接目标和链接源具有不同的组时,我们可以抑制链接强度。根据力布局的性质,可能需要改变指定的强度 - 更多相互连接的组可能需要具有较弱的组间链接强度。
要根据我们是否有组间链接来更改链接强度,我们可能会使用:
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));
我们想要将组节点强制在一起,为此我们需要知道组的质心。 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}
}
我们不需要调整每个节点 - 只是那些偏离了质心的节点。对于那些足够远的人,我们可以使用质心和节点当前位置的加权平均值来推动它们。
我修改用于确定在可视化冷却时是否应调整节点的最小距离。在可视化处于活动状态的大部分时间,当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;
}
})
这允许最简单的节点分组 - 它确保组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()))
并在每个刻度上更新:
// update voronoi:
cell = cell.data(voronoi.polygons(simulation.nodes())).attr("d", renderCell);
总而言之,在分组阶段看起来像这样:
随着可视化最终停止:
如果第一张图片更受欢迎,那么删除部件会随着alpha冷却而更改minDistance
。
此处使用上述方法a block。
我们可以使用另一个力图来定位每个组的理想质心,而不是使用每个组的节点的质心。该力图将具有每个组的节点,每个组之间的链路强度将对应于组的节点之间的te个链路。使用此力图,我们可以将原始节点强制转向我们理想化的质心 - 第二个力布局的节点。
这种方法在某些情况下可能具有优势,例如通过更大量地分组。这种方法可能会给你类似的东西:
我已经在此处添加了一个示例,但希望代码的评论能够充分理解,而不会像上面的代码一样进行细分。
voronoi很简单,但并不总是最美观,你可以使用剪辑路径将多边形剪切成某种椭圆形,或者使用渐变叠加层在多边形到达边缘时淡出多边形。根据图形复杂性可能的一种选择是使用最小凸多边形,尽管这对于节点少于三个的组不能很好地工作。在大多数情况下,边界框可能不会起作用,除非你真的将强制因子保持在高位(例如:在整个时间内保持minDistance
非常低)。权衡将永远是你想要展示的更多:连接或分组。