如何在拖动节点时,让连线随之移动

利用JavaScript的对象引用传递的这个特性,可以很好的实现【数据驱动】的理念。

大致逻辑就是:修改数据->调用render函数

如果想要做得更自动化一点,可以加入观察者模式,通过Object.defineProperty自动监测属性的变化,自动调用render方法,这样你只需关注数据的增删改查即可。

注意数据必须是对象,不能是基本数据类型。

给被拖拽的元素设置id属性

比如我是将circle和text,用一个g包裹起来了,那么我就给g元素设置id属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function drawNodesAndTexts(nodes) {
let nodeGroups = getContainer().selectAll('g')
.data(nodes)
.enter()
.append('g')
// 关键代码在这一行
.attr('id', n => `node_${n.id}`)
.attr('transform', (d) => {
return `translate(${d.x}, ${d.y})`;
})
// 给整个组绑定拖拽事件
.call(drag());
drawNodes(nodeGroups)
drawText(nodeGroups)
}

给线条设置属性,其值为关联的节点的ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function drawRelationships(relationships) {
let container = getContainer();
container.selectAll('line')
.data(relationships)
.enter()
.append('line')
.attr('x1', r => {
return nodeMap[r.from].x;
})
.attr('y1', r => {
return nodeMap[r.from].y;
})
.attr('x2', r => {
return nodeMap[r.to].x;
})
.attr('y2', r => {
return nodeMap[r.to].y;
})
// 关键代码是下面的两行
.attr('from', r => r.from)
.attr('to', r => r.to)
.attr('stroke', 'black')
.attr('stroke-width', '2px')
}

在drag处理函数中,修改line的x1、y1、x2、y2属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function move(d)
{
let x = d3.event.x,
y = d3.event.y;

if (!inBoundaries(x, y)) {
return false;
}
d3.select('#coordinate').html(`x=${x}, y=${y}`)

let element = d3.select(this);
element.raise()
.attr('transform', () => {
return `translate(${d.x = x}, ${d.y = y})`;
})

let id = element.attr('id');
if ('undefined' === typeof id) {
return;
}
let idInfo = id.split('_')
if ('undefined' === typeof idInfo[1]) {
return;
}
id = idInfo[1];

// 【经验教训】通过属性筛选元素,属性的值必须是一个字符串,不能是数字
d3.selectAll(`line[from="${id}"]`)
.attr('x1', x)
.attr('y1', y)

d3.selectAll(`line[to="${id}"]`)
.attr('x2', x)
.attr('y2', y)
}