字都居中了!
这个线怎么跟激光似的,一点也不像在遛舔狗
再来看看这个线,我们一开始是把所有代表弹簧力的线段当成直线就画上去了,但这样看起来很生硬效果很差。实际上我们需要的是一条自然的曲线把资源节点和应用节点连接起来,同时穿过信息节点,所以问题就变成了如何穿过三个点画一条曲线。
要画曲线自然要用到 svg 的 path 元素和他的 d 绘制指令,关于怎么用 path 画曲线,这里和MDN上都有很详细的教程。在具体实际项目应用中,一般来说贝塞尔曲线会比较难把控也比较难获得较好的效果,所以我们使用 A 指令来画这个弧线。
使用 A 指令画弧线,需要知道的元素有:x轴半径,y轴半径,弧形旋转角度,角度大小flag,弧线方向flag,弧形的终点。那在已知三个点坐标的情况下,怎么求出这些元素呢?是时候复习一波三角函数了。
已知 A、B、C 坐标(xaya、xbyb、xcyc),则可求得 a、b、c 长度(Math.sqrt((x1-x2)2 - (y1-y2)2),再根据余弦定理可求得∠C,再根据正弦定理可得r,具体参看代码:
type IVisualLink = { id: string; start: number[]; middle: number[]; end: number[]; arcPath: string; hasReverseVisualLink: boolean; }; const visualLinks: IVisualLink[] = [...]; function dist(a: number[], b: number[]) { return Math.sqrt( Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2)); } ... const pathElements = svg.append('g') .selectAll('path') .data(visualLinks) .enter().append('path') .attr('fill', 'none') .attr('stroke-width', 1) .attr('stroke', '#E5E5E5'); ... 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 arcPath = ['M', A, 'A', r, r, 0, laf, saf, B].join(' '); visualLinks[idx].arcPath = arcPath; }); pathElements .attr('d', (link) => { return link.arcPath; }); }
内容版权声明:除非注明,否则皆为本站原创文章。