折线
要画折线先要确定这条线相关联的点的坐标,长宽以及连入连出的方向。
- 节点
{
id:1
x: 100,
y: 100,
w: 20,
h: 20
}
- 连接线
{
id:'1'
source: 1,
target: 101,
sourceDirection: 'TOP',
targetDirection: 'TOP'//top, left, right, bottom
}
美学原则
为了产生更匀称、更自然,尽少和其他线条交叉的折线来,我们需要约定几个原则。
- 折线边和所连的节点之间应垂直连入
- 除了垂直连入的线段外,折线和节点间应保持一定的距离(offset)
- 折线转折的地方尽可能少
- 连线尽量不和节点交叉
- 保证 3 的前提下,尽可能选择这样的地方转折:
- 两个节点的中间位置
- 平行于节点的外边框(包含 offset 的部分)的位置
现在我们需要一个方法求出这些转折点point
代码语言:javascript复制const findpath=(sourceNode, targetNode, sourceDirection, targetDirection, offset)=>{
return [...points]
}
向量判断法
每个节点有4个方向,所以连接方向可能是(上上,上右,上下,上左,右右,右下,右左,下下,下左,左左)10种连接方向
确定了节点和连入方向,我们可以计算图中的转折点坐标。
以(上上)连接方向为例子
(上上)连接的时候以y轴比较小的节点为固定点
分别有3种情况
- targetPoint x轴在固定box左侧
- targetPoint x轴在固定box右侧
- targetPoint x轴在固定box中间(小于固定box 中心点往做绕,大于固定box 中心点 往右绕)
代码
代码语言:javascript复制//情况1:两个点都向上
var points = [];
var from = sourceNode;//开始点
var to = targetNode;//结束点
var offset = 30;
var fixed, active, reverse; //固定点、移动点、是否需要逆序
var fixedProps
var activeProps
if (from.y < to.y) {
fixed = from;
active = to;
fixedProps=sourceNode;
activeProps=targetNode;
reverse = false;
} else {
fixed = to;
active = from;
fixedProps=targetNode;
activeProps=sourceNode;
reverse = true;
}
if (
active.x >= fixedProps.x - offset &&
active.x <= fixedProps.x fixedProps.w offset
) {//活动点固定点中间
var x;
if (active.x < fixedProps.x fixedProps.w / 2) {
x = fixedProps.x - offset;
} else {
x = fixedProps.x fixedProps.w offset;
}
var y = fixed.y - offset;
points.push({ x: fixed.x, y: y });
points.push({ x: x, y: y });
y = active.y - offset;
points.push({ x: x, y: y });
points.push({ x: active.x, y: y });
} else {
var y = fixed.y - offset;
points.push({ x: fixed.x, y: y });
points.push({ x: active.x, y: y });
}
曲线
关于贝塞尔曲线可以看下张鑫旭的博客深度掌握SVG路径path的贝塞尔曲线指令
贝塞尔三次曲线例子
已知当前折线的路径坐标
[{x:50,y:50},{x:50,y:200},{x:200,y:200},{X:200,y:350}]
当前的折线路径为
<path d="M50 50 L50 200 L200 200 L200 350"/>
贝塞尔三次曲线
C 后面2个坐标为控制点,并不在实际路径上,最后一个点为目标点(正所谓虚虚实实)
曲线路径为
贝塞尔二次曲线
贝塞尔二次曲线例子
Q后面1个坐标为控制点,并不在实际路径上,第二个点为目标点
简单思路:对一条折线(起点、终点和折点的数组),按顺序每次取三个连续点,如果三个连续点组成了一个直角,那么就在中间点附近增加距离为 radius (固定值为50)的两个点,由原中间点为控制点,组成一个圆弧。
由原先 MA->LB→LC→LD
转变为 MA->Lb1->LB->Lb2→Lc1->LC->Lc2→LD
转变为 MA->Lb1→QB b2→Lc1→QC c2→LD
那么接下来就是求转折点附近的2个点坐标
代码语言:javascript复制//三个连续点组成了一个直角
const getTurnPoints=(points, box, radius = 20)=>{
return _.reduce(
points,
(result, point, index) => {
if (index === 0) {
result.push(['M', point.x - box.x, point.y - box.y]);
} else if (index === points.length - 1) {
result.push(['L', point.x - box.x, point.y - box.y]);
} else {
const pointB = point;
const pointA = points[index - 1];
const pointC = points[index 1];
let b1 = {};
if (pointA.x == pointB.x) {
b1.x = pointB.x;
if (pointA.y < pointB.y) {
b1.y = pointB.y - radius;
} else {
b1.y = pointB.y radius;
}
} else {
b1.y = pointB.y;
if (pointA.x < pointB.x) {
b1.x = pointB.x - radius;
} else {
b1.x = pointB.x radius;
}
}
let b2 = {};
if (pointB.x == pointC.x) {
b2.x = pointB.x;
if (pointB.y < pointC.y) {
b2.y = pointB.y radius;
} else {
b2.y = pointB.y - radius;
}
} else {
b2.y = pointB.y;
if (pointB.x < pointC.x) {
b2.x = pointB.x radius;
} else {
b2.x = pointB.x - radius;
}
}
result.push(['L', b1.x - box.x, b1.y - box.y]);
result.push(['Q', pointB.x - box.x, pointB.y - box.y, b2.x - box.x, b2.y - box.y]);
}
return result;
},
[]
);
}
实现效果
参考
- 路径-SVG
- SVG矢量绘图 path路径详解(贝塞尔曲线及平滑)
- 一种在关系图中画带圆角折线连线的策略