# LogicFlow 节点缩放

# 前言

LogicFlow 目前内置的节点类型有矩形、圆形、菱形、多边形、椭圆、文本。通过继承这些基本类型可以实现自定义节点,对其功能进行扩展。 节点缩放就是通过自定义节点方式实现的,本文将详细介绍节点缩放功能的实现方案。

# 支持的节点类型

目前节点缩放支持的节点类型如下:

  • 矩形
  • 椭圆
  • 菱形

为什么要实现这3种节点类型呢?
流程图中常用的节点类型是矩形、圆形、菱形,因此仅目前仅支持了3种比较常用的。如果你的系统需要其他类型,可以参考本文的思路,自定义节点实现。

为什么是椭圆不是圆形?
椭圆可以通过将rx,ry设置为相同的值来绘制圆形,圆形改变大小会变成椭圆,因此这里对椭圆的缩放实现。

# 缩放效果

目前缩放是在节点边框四角方向上,通过拖拽进行大小调整,效果如下。想要了解如何在项目中使用参考节点缩放使用文档

# 缩放实现方案

# 缩放操作

通过继承基础类型节点,重写节点绘制方法(getShape), 在节点四角上增加四个控制点,点击控制点拖拽进行缩放。 控制点的实现与@logicflow/core保持一致,基于Preact进行绘制。

# 控制点拖拽

控制点拖拽后,需要有4方面的调整

  • 节点位置(x,y)
  • 节点文案位置(textPosition)
  • 节点大小(width,height/rx,ry)
  • 与节点相连的边,路径调整(pointsList)

LogicFlow的绘制是MVVM模式,绘制(view)上的调整,更新数据(model)即可。

# 节点位置 & 节点文案位置

根据控制点移动的距离,节点中心点位置和文案位置移动对应一半的距离。

updatePosition = ({ deltaX, deltaY }) => {
  const { x, y } = this.nodeModel;
  this.nodeModel.x = x + deltaX / 2;
  this.nodeModel.y = y + deltaY / 2;
  this.nodeModel.moveText(deltaX / 2, deltaY / 2);
};

# 节点大小

根据控制点移动的距离,节点的宽高对应增加或较少对应的距离。矩形修改其宽高,菱形和椭圆修改其 rx/ry 取值,菱形和椭圆的宽高是以及 rx/ry 得到的计算属性,自动更新。距离增加逻辑根据控制点 (control) 位置,以及移动我位置计算方式如下。
index: 控制点顺序index, 顺序如下【左上,右上,右下,左下】
deltaX/deltaY: 控制点移动位置
pct: width, height, rx, ry 需要计算的比例,矩形为1,椭圆菱形为1/2。

// 计算control拖动后,节点的宽高
  getResize = ({ index, deltaX, deltaY, width, height, pct = 1 }) => {
    const resize = { width, height };
    switch (index) {
      case 0:
        resize.width = width - deltaX * pct;
        resize.height = height - deltaY * pct;
        break;
      case 1:
        resize.width = width + deltaX * pct;
        resize.height = height - deltaY * pct;
        break;
      case 2:
        resize.width = width + deltaX;
        resize.height = height + deltaY * pct;
        break;
      case 3:
        resize.width = width - deltaX * pct;
        resize.height = height + deltaY * pct;
        break;
      default:
        break;
    }
    return resize;
  };

得到resize之后,更新数据。

  • 矩形: width = resize.width; height = resize.height;
  • 椭圆: rx = resize.width; ry = resize.height;
  • 菱形: rx = resize.width; ry = resize.height;

# 与节点相连的边,路径调整

当节点位置和大小更新之后,如果节点与其他节点之间存在连线,那么连线的路径也要做相对的调整。当边从节点连出时,根据边提供的方法,只需要更新边起点位置,路径就会自动更新,同理边连入节点时,更新边重点位置即可。以矩形为例如下:

let afterPoint;
// 获取所有与节点相连的边
const edges = this.getNodeEdges(id);
// 更新从节点连出边的起点
edges.sourceEdges.forEach(item => {
  params.point = item.startPoint;
  afterPoint = getRectReizeEdgePoint(params);
  item.updateStartPoint(afterPoint);
});
// 更新连入节点边的终点
edges.targetEdges.forEach(item => {
  params.point = item.endPoint;
  afterPoint = getRectReizeEdgePoint(params);
  item.updateEndPoint(afterPoint);
});

节点缩放后,需要计算边起点终点的新坐标,计算思路是获取节点在缩放前在节点上的的相对位置,例如:与中心点的夹角、在节点某条边框的相对位置等,依据该相对位置比例,计算节点缩放后的该点的新坐标。缩放连线调整部分介绍详细的计算方法。

# 缩放连线调整

矩形、椭圆、菱形在图形数据和绘制上不同,因此计算方法不同,这也是节点缩放实现最复杂的部分,下面将分别介绍详细的计算方法。

# 矩形

将矩形中心当做中心点(0,0),矩形支持radius取值,存在圆角矩形,将端点在矩形直线边和圆角两种情况进行计算,逻辑如下。
矩形 矩形resize

# 椭圆

将椭圆中心当做中心点(0,0),计算缩放前边的端点与X轴的夹角θ,缩放后保持夹角θ不变计算新坐标。 椭圆resize

# 菱形

将菱形中心当做中心点(0,0), 如下图所示,首先计算点P到点E的距离L,然后计算出L占NE距离的比例pct,缩放后保持pct不变计算新坐标。当点P坐标大于0时以点E作为参考点进行比例计算,当点P坐标小于0时,以点W作为参考点进行比例计算。 菱形resize

# 个性化配置

# 缩放范围

节点设置缩放的范围,当拖动控制点调整大小达到最大或最小值时,节点大小不会再改变,支持的配置以及默认取值如下。

   // 缩放范围
  sizeRange: {
    rect: {
      minWidth: 30,
      minHeight: 30,
      maxWidth: 300,
      maxHeight: 300,
    },
    ellipse: {
      minRx: 15,
      minRy: 15,
      maxRx: 150,
      maxRy: 150,
    },
    diamond: {
      minRx: 15,
      minRy: 15,
      maxRx: 150,
      maxRy: 150,
    },
  },

# 拖动step

当拖动step=n时候,节点坐标会更新 step/2= n/2。step默认取值为2,当设置了网格grid之后,默认取值为 2 * grid。

  • 默认取值为2,是为了保证缩放后节点坐标为证书
  • 设置了grid之后,为了能够保证能够依然高效实用对齐线功能,因此step默认设置为2 * grid,由此也会带来一些问题,当grid取值为10以上的值时,操作上会感觉节点缩放不太流畅。这个时候也可以手动修改step值,这个时候需要宿主系统功能上做下权衡取舍。

# 样式

增加节点调整后,为了使整体样式个更加舒适,在插件内部设置了节点的主题样式,宿主可以对其进行覆盖设置。

// 设置默认样式,主要将outlineColor设置为透明,不再展示core包中默认的节点外框
    lf.setTheme({
      rect: {
        strokeWidth: 2,
        outlineColor: 'transparent',
      },
      ellipse: {
        strokeWidth: 2,
        outlineColor: 'transparent',
      },
      diamond: {
        strokeWidth: 2,
        outlineColor: 'transparent',
      },
    });

为了能让宿主自由调整一些样式,支持节点缩放边框以及控制点样式调整,支持的样式以及默认值如下。

// 边框和contol拖动点样式的设置
  style: {
    outline: {
      stroke: '#000000',
      strokeWidth: 1,
      strokeDasharray: '3,3',
    },
    controlPoint: {
      width: 7,
      height: 7,
      fill: '#FFFFFF',
      stroke: '#000000',
    },
  },

# 事件

节点缩放后,定义了 node:resize 事件,并抛出节点缩放前和缩放后的基础信息、大小、位置信息,方便宿主可以进行其他操作。

# 自定义节点使用

为了能够使自定义节点使用缩放功能,内部将 RectResize, EllipseResize , DiamondResize 导出,通过继承 RectResize.model , RectResize.view 等实现缩放。

# 最后

以上介绍了节点缩放功能的实现方案,如果对此插件实现有想法的同学,欢迎在用户群交流~。