前端可视化建模中的连线策略

2021-11-15 14:48:13 浏览数 (1)

折线

要画折线先要确定这条线相关联的点的坐标,长宽以及连入连出的方向。

  • 节点
代码语言:javascript复制
{
  id:1
  x: 100,
  y: 100,
  w: 20,
  h: 20
}
  • 连接线
代码语言:javascript复制
{
  id:'1'
  source: 1,
  target: 101,
  sourceDirection: 'TOP',
  targetDirection: 'TOP'//top, left, right, bottom 
}

美学原则

为了产生更匀称、更自然,尽少和其他线条交叉的折线来,我们需要约定几个原则。

  1. 折线边和所连的节点之间应垂直连入
  2. 除了垂直连入的线段外,折线和节点间应保持一定的距离(offset)
  3. 折线转折的地方尽可能少
  4. 连线尽量不和节点交叉
  5. 保证 3 的前提下,尽可能选择这样的地方转折:
    1. 两个节点的中间位置
    2. 平行于节点的外边框(包含 offset 的部分)的位置

现在我们需要一个方法求出这些转折点point

代码语言:javascript复制
const findpath=(sourceNode, targetNode, sourceDirection, targetDirection, offset)=>{
	return [...points]
}

向量判断法

每个节点有4个方向,所以连接方向可能是(上上,上右,上下,上左,右右,右下,右左,下下,下左,左左)10种连接方向

确定了节点和连入方向,我们可以计算图中的转折点坐标。

以(上上)连接方向为例子

(上上)连接的时候以y轴比较小的节点为固定点

分别有3种情况

  1. targetPoint x轴在固定box左侧
  2. targetPoint x轴在固定box右侧
  3. 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路径详解(贝塞尔曲线及平滑)
  • 一种在关系图中画带圆角折线连线的策略

0 人点赞