import intersect from 'path-intersection'; const render = () => { ... nodes // 过滤出所有的信息节点 .filter(node => !node.isAppNode) .forEach((node) => { ... // 根据信息节点的信息得到对应的 visualLink 对象 index const idx = findVisualLinkIndex(node) visualLinks[idx].start = [source.x!, source.y!]; visualLinks[idx].middle = [node.x!, node.y!]; visualLinks[idx].end = [target.x!, target.y!]; const A = visualLinks[idx].start; const B = visualLinks[idx].end; const C = visualLinks[idx].middle; const a = dist(B, C); const b = dist(C, A); const c = dist(A, B); // 余弦定理求得∠C const angle = Math.acos((a * a + b * b - c * c) / (2 * a * b)); // 正弦定理求得外接圆半径 const r = _.round(c / Math.sin(angle) / 2, 4); // 角度大小flag,因为我们要的是条弧线而不是一个残缺的圆,所以恒为0 const laf = 0; // 弧线方向flag,根据AB的斜率判断C在AB线的那一边,再确定弧线方向 const saf = +((B[0] - A[0]) * (C[1] - A[1]) - (B[1] - A[1]) * (C[0] - A[0]) < 0); const origArcPath = ['M', A, 'A', r, r, 0, laf, saf, B].join(' '); const raidus = NODE_RADIUS; const startCirclePath = [ 'M', A, 'm', [-raidus, 0], 'a', raidus, raidus, 0, 1, 0, [raidus * 2, 0], 'a', raidus, raidus, 0, 1, 0, [-raidus * 2, 0], ].join(' '); const endCirclePath = [ 'M', B, 'm', [-raidus, 0], 'a', raidus, raidus, 0, 1, 0, [raidus * 2, 0], 'a', raidus, raidus, 0, 1, 0, [-raidus * 2, 0], ].join(' '); const startIntersection = intersect(origArcPath, startCirclePath)[0]; const endIntersection = intersect(origArcPath, endCirclePath)[0]; const arcPath = [ 'M', [startIntersection.x, startIntersection.y], 'A', r, r, 0, laf, saf, [endIntersection.x, endIntersection.y], ].join(' '); visualLinks[idx].arcPath = arcPath; }); pathElements .attr('d', (link) => { return link.arcPath; }); ... }
效果已经很接近了!
字叠到一起啦,臣妾看不清啊
到这一步整体效果其实已经差不多了,但追求完美的我们怎么可能到此为止呢?仔细看看这个图,因为调用信息是一个方盒而不是原型的节点,如果应用和资源间有来有往,那这个字很容易叠到一起。可以尝试调整碰撞力(Collision)和弹簧力(Links)来让他们别叠到一起,不过试下来发现调整这两个系数很容易把整个图弄得乱七八糟的。那咋办呢?我们就要到此为止了吗?不妨换个思路,如果应用与资源间有来有往,则这个连接信息就不放到中间点,而是放到开始三分之一处。