使用 dagre-d3 或 colajs 的 d3js 流程图
flowcharts in d3js using dagre-d3 or colajs
看到dagre-d3相当复杂的TCP state diagram example后,我认为它能够解决类似复杂度的图表。
在下图中,情况显然不是这样。如果交换两个标记的节点,则所有交叉点都将得到解决。
另一个有趣的事情是,图形的求解效果似乎取决于我设置边的顺序。
以下代码
g.setEdge("148570019_1100", "148570020_1100", { label: "" });
g.setEdge("148570019_1100", "148570021_1100", { label: "" });
g.setEdge("148570019_1100", "148570010_1100", { label: "" });
没有给出与此相同的结果:
g.setEdge("148570019_1100", "148570010_1100", { label: "" });
g.setEdge("148570019_1100", "148570020_1100", { label: "" });
g.setEdge("148570019_1100", "148570021_1100", { label: "" });
看这两个例子:
按照建议,我尝试改用 cola.js,这就是同一张图的样子:
如您所见,colajs 更擅长减少交叉,但布局不如 dagre 的结构清晰。我对 colajs 使用以下设置:
cola.d3adaptor()
.avoidOverlaps(true)
.convergenceThreshold(1e-3)
.symmetricDiffLinkLengths(20)
.flowLayout('x', 150)
.size([width, height])
.nodes(nodes)
.links(edges)
.constraints(constraints)
.jaccardLinkLengths(300);
是否可以配置 colajs 使其看起来更有条理,类似于 dagre?是 dagre 根本无法解决这样的问题,还是可以按原样配置它?
这是问题的一种解决方案:http://jsfiddle.net/5u9mzfse/
或多或少,我只是对确定最佳渲染的实际问题感兴趣,以及如何通过算法实现这一点。
这个想法是利用渲染节点的顺序很重要这一事实,因此您可以打乱顺序并找到产生最佳结果的顺序。最简单的方法是测试边缘形成的线的边界框是否发生碰撞。在这里,我假设边的开始和结束是边界框的足够好的估计。
应先将边保存到列表中
var edgeList = [["10007154_1100","148570017_1100",{"label":""}, ...]
然后
- 随机播放列表
- 渲染节点
- 根据输出计算边缘的边界框
- 计算有多少边界框重叠
- 如果碰撞计数为零则渲染输出,否则继续直到max_cnt次迭代已经运行和select最好所以远
渲染输出的边缘边界框可以使用如下方式找到:
var nn = svg.select(".edgePaths");
var paths = nn[0][0];
var fc = paths.firstChild;
var boxes = [];
while(fc) {
var path = fc.firstChild.getAttribute("d");
var coords = path.split(/,|L/).map(function(c) {
var n = c;
if((c[0]=="M" || c[0]=="L")) n = c.substring(1);
return parseFloat(n);
})
boxes.push({ left : coords[0], top : coords[1],
right : coords[coords.length-2],
bottom : coords[coords.length-1]});
fc = fc.nextSibling;
}
你只是计算盒子是否碰撞,我认为这样的结果大致正确:
var collisionCnt = 0;
boxes.forEach( function(a) {
// --> test for collisions against other nodes...
boxes.forEach(function(b) {
if(a==b) return;
// test if outside
if ( (a.right < b.left) ||
(a.left > b.right) ||
(a.top > b.bottom) ||
(a.bottom < b.top) ) {
// test if inside
if(a.left >= b.left && a.left <=b.right
|| a.right >= b.left && a.right <=b.right) {
if(a.top <= b.top && a.top >= b.bottom) {
collisionCnt++;
}
if(a.bottom <= b.top && a.bottom >= b.bottom) {
collisionCnt++;
}
}
} else {
collisionCnt++;
}
})
})
那么你就知道有多少条边与这组边相交了。
然后在每一轮之后检查这是否是我们迄今为止最好的阵列,或者如果没有碰撞立即退出;
if(collisionCnt==0) {
optimalArray = list.slice();
console.log("Iteration cnt ", iter_cnt);
break;
}
if(typeof(best_result) == "undefined") {
best_result = collisionCnt;
} else {
if(collisionCnt < best_result) {
optimalArray = list.slice();
best_result = collisionCnt;
}
}
在至少使用简单图进行测试期间,该算法需要 1-5 轮来计算边的最佳顺序,因此看起来它可能工作得很好,至少如果图不是太大的话。
看到dagre-d3相当复杂的TCP state diagram example后,我认为它能够解决类似复杂度的图表。
在下图中,情况显然不是这样。如果交换两个标记的节点,则所有交叉点都将得到解决。
另一个有趣的事情是,图形的求解效果似乎取决于我设置边的顺序。
以下代码
g.setEdge("148570019_1100", "148570020_1100", { label: "" });
g.setEdge("148570019_1100", "148570021_1100", { label: "" });
g.setEdge("148570019_1100", "148570010_1100", { label: "" });
没有给出与此相同的结果:
g.setEdge("148570019_1100", "148570010_1100", { label: "" });
g.setEdge("148570019_1100", "148570020_1100", { label: "" });
g.setEdge("148570019_1100", "148570021_1100", { label: "" });
看这两个例子:
按照建议,我尝试改用 cola.js,这就是同一张图的样子:
如您所见,colajs 更擅长减少交叉,但布局不如 dagre 的结构清晰。我对 colajs 使用以下设置:
cola.d3adaptor()
.avoidOverlaps(true)
.convergenceThreshold(1e-3)
.symmetricDiffLinkLengths(20)
.flowLayout('x', 150)
.size([width, height])
.nodes(nodes)
.links(edges)
.constraints(constraints)
.jaccardLinkLengths(300);
是否可以配置 colajs 使其看起来更有条理,类似于 dagre?是 dagre 根本无法解决这样的问题,还是可以按原样配置它?
这是问题的一种解决方案:http://jsfiddle.net/5u9mzfse/
或多或少,我只是对确定最佳渲染的实际问题感兴趣,以及如何通过算法实现这一点。
这个想法是利用渲染节点的顺序很重要这一事实,因此您可以打乱顺序并找到产生最佳结果的顺序。最简单的方法是测试边缘形成的线的边界框是否发生碰撞。在这里,我假设边的开始和结束是边界框的足够好的估计。
应先将边保存到列表中
var edgeList = [["10007154_1100","148570017_1100",{"label":""}, ...]
然后
- 随机播放列表
- 渲染节点
- 根据输出计算边缘的边界框
- 计算有多少边界框重叠
- 如果碰撞计数为零则渲染输出,否则继续直到max_cnt次迭代已经运行和select最好所以远
渲染输出的边缘边界框可以使用如下方式找到:
var nn = svg.select(".edgePaths");
var paths = nn[0][0];
var fc = paths.firstChild;
var boxes = [];
while(fc) {
var path = fc.firstChild.getAttribute("d");
var coords = path.split(/,|L/).map(function(c) {
var n = c;
if((c[0]=="M" || c[0]=="L")) n = c.substring(1);
return parseFloat(n);
})
boxes.push({ left : coords[0], top : coords[1],
right : coords[coords.length-2],
bottom : coords[coords.length-1]});
fc = fc.nextSibling;
}
你只是计算盒子是否碰撞,我认为这样的结果大致正确:
var collisionCnt = 0;
boxes.forEach( function(a) {
// --> test for collisions against other nodes...
boxes.forEach(function(b) {
if(a==b) return;
// test if outside
if ( (a.right < b.left) ||
(a.left > b.right) ||
(a.top > b.bottom) ||
(a.bottom < b.top) ) {
// test if inside
if(a.left >= b.left && a.left <=b.right
|| a.right >= b.left && a.right <=b.right) {
if(a.top <= b.top && a.top >= b.bottom) {
collisionCnt++;
}
if(a.bottom <= b.top && a.bottom >= b.bottom) {
collisionCnt++;
}
}
} else {
collisionCnt++;
}
})
})
那么你就知道有多少条边与这组边相交了。
然后在每一轮之后检查这是否是我们迄今为止最好的阵列,或者如果没有碰撞立即退出;
if(collisionCnt==0) {
optimalArray = list.slice();
console.log("Iteration cnt ", iter_cnt);
break;
}
if(typeof(best_result) == "undefined") {
best_result = collisionCnt;
} else {
if(collisionCnt < best_result) {
optimalArray = list.slice();
best_result = collisionCnt;
}
}
在至少使用简单图进行测试期间,该算法需要 1-5 轮来计算边的最佳顺序,因此看起来它可能工作得很好,至少如果图不是太大的话。