好的,所以我想我会试一试,并用一些矢量数学来实现它,它更漂亮,结果是可重用的。
一些澄清:
- “向量”只是两个数字(x 和 y)
- “坐标”在结构上与向量相同,只是对我们有不同的含义。尽管如此,我们可以运行相同的数学运算。
- “定位向量”是两个向量(如源和目标)
- 您可以通过从第二个向量中减去第一个向量来“释放”定位向量(您会得到一个不再锚定在坐标系中的新向量)
- 使用毕达哥拉斯定理(也称为范数)计算向量的“长度”
- “向量加法”就是简单地将两个或多个向量的 xs 和 ys 相加,得到一个新向量。
- “标量乘法”和除法是通过将 x 和 y 与标量相除来完成的
- “单位向量”是向量除以其长度
假设我们希望它动态工作(“每滴答声”),初始链接调整看起来像这样(我使用的是 coffeescript):
links.attr('x1', ({source,target}) -> source.x)
.attr('y1', ({source,target}) -> source.y)
.attr('x2', ({source,target}) -> target.x)
.attr('y2', ({source,target}) -> target.y)
我们要做的是将源和目标nodeRadius
移离圆圈。为此,我们使用向量数学来
- 释放定位向量(链接由两个坐标组成,我们想要一个未锚定的向量)
- 计算自由向量的单位向量
- 一旦我们有了它,我们就可以将它乘以
nodeRadius
. 这个新向量表示节点中心与其边界之间的距离,与链接的方向相同。
- 将向量添加到源坐标,这些新坐标将位于圆的边缘。
好的,所以我们将使用以下函数来执行此操作:
length = ({x,y}) -> Math.sqrt(x*x + y*y)
sum = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1+x2, y:y1+y2}
diff = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1-x2, y:y1-y2}
prod = ({x,y}, scalar) -> {x:x*scalar, y:y*scalar}
div = ({x,y}, scalar) -> {x:x/scalar, y:y/scalar}
unit = (vector) -> div(vector, length(vector))
scale = (vector, scalar) -> prod(unit(vector), scalar)
free = ([coord1, coord2]) -> diff(coord2, coord1)
这可能看起来有点压倒性,它非常紧凑,因为coffeescript允许我们直接在方法签名中解构东西,非常方便!如您所见,还有另一个名为scale
. 将步骤 2. & 3 结合起来只是一个方便的功能。
现在让我们尝试为链接源设置新的 x 坐标。请记住:坐标应移动nodeRadius
,以便它从圆的边界开始,而不是在圆内。
(d) ->
# Step 1
freed = free(d)
# Step 2
unit = unit(freed)
# Step 3
scaled = prod(unit, nodeRadius)
# Step 2+3 would be scale(freed, nodeRadius)
# Step 4, coords are pretty much just vectors,
# so we just use the sum() function to move the source coords
coords = sum(d.source, scaled)
return coords.x
没什么!将所有这些放入tick()
函数中,我们得到:
links.attr('x1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).x)
.attr('y1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).y)
.attr('x2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).x)
.attr('y2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).y)
哦,别忘了从目标坐标中减去,否则你只会让这条线再次变长(即移动它nodeRadius
)。