d3重力布局——节点间多关系处理
相关的知识:
path相关的知识
<path>标签是d3里面功能最为丰富的标签,很多图形用该标签可以制作出来。它通过一系列的坐标点来绘制。在d3的图形绘制中。经常会用到。
用法:给出一个坐标点,在坐标点前面添加一个英文字母,表示如何运动到此坐标点。
英文字母的按照功能可分为5类:
- 移动类
- M = moveto:将画笔移动到指定坐标
- 直线类
- L = lineto:画直线到指定坐标。
- H = horizontal lineto:画水平线到指定坐标。
- V = vertical lineto:画垂直线到指定坐标。
- 曲线类
- C = curveto:画三次贝塞尔曲线经两个指定控制点到达最终坐标。
- S = shorthand/smooth curveto:与前一条三次贝塞尔曲线,第一个控制点为前一条曲线第二个控制点的对称点,只需要输入第二个控制点和终点,即可绘制一个三次贝塞尔曲线
- Q = quadratic Bezier curveto:画一个二次贝塞尔曲线经过一个指定控制点到达终点坐标
- T = Shorthand/smooth quadratic Bezier curveto:与前一条贝塞尔曲线相连,控制点为前一条二次贝塞尔曲线控制点的对称点。只需输入终点,即可绘制一个二次贝塞尔曲线。
- 弧线类
- A = elliptical arc:画椭圆曲线到指定坐标
- 闭合类
- Z = closepath:绘制一条直线,连接终点和起点,用来封闭图形。
到这里,我们知道了绘制一个<path>所要知道的基本的英文字母的基本意思
问题解析
然后就开始处理我们的核心问题:节点间的多关系
在实际应用场景中,人与人之间的关系不仅仅是单向,更多的是多项的,例如:小明 — 朋友 — 小红 ,小明 — 同学 — 小红 ;或者 小红— 朋友 — 小明 ,小红 — 同学 — 小明。
如果在d3的重力导向布局中如果不对这种节点间的关系做处理,很多关系会叠加载一块,影响美观与观感。
在现有的数据可视化框架中也有相关案例,例如echarts。你只需要设置lineStyle的属性curveness的值就可以使得线条呈现一个弧度。但是由于项目的特殊性,很多情况下需要我们用d3来完成自由度更高,并且一些个定制化的服务。
因此,在d3关于节点间的多关系处理中,曲线连接是一种比较好的方案。因此解决问题的核心在于如何绘制曲线,并且保证两点之间的多条线不会覆盖?在多条线弯曲下,如何平均半圆弧弯曲避免全跑到某半圆弧上?定义曲线弧方向?
首先统计下两点之间的线条数,再将这些连接线分配到一个 map 里,两个节点的 name 字段进行拼接做成 key,这样计算得到两点之间的连接线总数。
然后在遍历时同 map 的线根据方向分成正向、反向两组,正向组遍历给每条线追加设置一个 linknum 编号,同理,反向组遍历追加一个 -linknum 编号值。这个正向、反向判断方法很多,根据节点 source.name、target.name 进行比较,这里其实是比较 ASCII 码。而我们设定的 linknum 值就是来确定该条弧线的弯曲度和弯曲方向的,这里搭配下面代码讲解比较好理解:
关系处理
//在vue的methods的方法中
dealRealtions(links) {
var linkGroup = {
}; //用来分组,将两点之间的连线进行归类
var linkMap = {
}; //对连接线的计数
for (var i = 0; i < links.length; i++) {
var key = links[i].source.name < links[i].target.name ? links[i].source.name + ':' + links[i].target.name : links[i].target.name + ':' + links[i].source.name;
if (!linkMap.hasOwnProperty(key)) {
linkMap[key] = 0 ;
}
linkMap[key] += 1;
if (!linkGroup.hasOwnProperty(key)) {
linkGroup[key] = [];
}
linkGroup[key].push(links[i]