Logicflow箭头源码分析

557 阅读4分钟

LogicFlow中箭头的绘制,是通过SVG中的maker元素实现的,logicflow中的直线,折线,曲线目前都支持在起点和终点设置箭头。

源码分析

Logicflow的底层架构是MVVM模式,箭头的绘制涉及到数据层Model和视图层View两部分。

Model

在baseEdgeModel里定义了arrowConfig属性,来存储起点终点箭头marker的Id。这个id需要跟view中定义marker的id一样,才能渲染出来。同时为了保证能够在流程图中多条连线markerId仍然能够标识正确,使用节点id作为其中一部分以保证唯一性。

LineEdgeModel(直线)、PolylineEdgeModel(折线)、BezierEdgeModel(曲线)继承自baseEdgeModel,因此其model中也是arrowConfig也标识箭头属性数据,arrowConfig有两个属性。

  • markerEnd:连线终点箭头的markerId,默认是有值的,也就是连线默认展示终点箭头。
  • markerStart:连线起点箭头的markerId,默认为空,也就是连线默认不展示起点箭头。如果想要展示起点箭头,需要在自定义连线时设置markerStart为url(#marker-start-${this.id})

markerEnd,markerStart的取值需要跟View中的getArrow实现的markerId对应起来,上面取得值是默认定义的值,如果复写getArrow方法并修改其中marker的id规则,需要在model中一并修改赋值。

 @observable arrowConfig = {
    markerEnd: `url(#marker-end-${this.id})`,
    markerStart: '',
  }; // 箭头属性

View

在箭头的View实现中主要涉及到这几个方法:

  • getArrow:获取箭头
  • getArrowStyle:获取箭头样式
  • getStartArrow:获取起点箭头
  • getEndArrow:获取终点箭头

其中在连线视图中调用的是getArrow方法来绘制,getArrow调用getArrowStyle,getStartArrow,getEndArrow方法来进行细节实现,下面我们来分析下源码实现。

getArrow

getArrow中为每个连线定义了两个maker来实现起终点的箭头,从源代码中可以看到,两个maker的id与model中的id是一致的,如何让连线和这些maker对应上的呢?在LineEdge、PolylineEdge、BezierEdge中图形绘制中将model中的markerStart,markerEnd属性赋值到了图形绘制中,参见下边直线的绘制,这个实现是借助了svg标签支持的,详见marker

同时也看到起点箭头已经绘制好了,如果想要展示出来,只要在model中进行赋值即可。

可以看到下面还有有refY refX 两个值,这两个值是用来标识箭头相对于起点和终点的偏移量,当你需要箭头不要紧紧贴着节点的时候可以使用这两个值进行调节。

getArrow(): h.JSX.Element | null {
    const { model: { id } } = this.props;
    const { refY = 0, refX = 2 } = this.getArrowStyle();
    return (
      <g>
        <defs>
          <marker id={`marker-start-${id}`} refX={-refX} refY={refY} overflow="visible" orient="auto" markerUnits="userSpaceOnUse">
            {this.getStartArrow()}
          </marker>
          <marker id={`marker-end-${id}`} refX={refX} refY={refY} overflow="visible" orient="auto" markerUnits="userSpaceOnUse">
            {this.getEndArrow()}
          </marker>
        </defs>
      </g>
    );
  }

直线绘制箭头

<Line
        {
          ...style
        }
        x1={startPoint.x}
        y1={startPoint.y}
        x2={endPoint.x}
        y2={endPoint.y}
        {...arrowConfig} // 箭头属性 markerEnd markerStart
        {
          ...isAnimation ? {
            strokeDasharray,
            stroke,
            style: {
              strokeDashoffset,
              animationName,
              animationDuration,
              animationIterationCount,
              animationTimingFunction,
              animationDirection,
            },
          } : {}
        }
      />

getEndArrow

getEndArrow返回的是一个path,也就是箭头的路径,其中path的属性是从getArrowStyle中获得加以计算得到的。

分析箭头路径,d={M 0 0 L ${offset} -${verticalLength} L ${offset} ${verticalLength} Z},从位置0,0出发到${offset} -${verticalLength}然后到 ${offset} ${verticalLength} 闭合。这就是一个三角形,默认的offset和verticalLength分别是10, 5。如果想要更大的或者细长的箭头,可以通过主题设置修改这两个值实现。

如下面坐标的图所示,这个图形可以直接用于起点箭头(也就是这样实现的),终点的话需要翻转180度。箭头样式属性中还有stroke、fill、strokeWidth、transform来修饰。这些属性由getArrowStyle返回,下面会介绍这个函数返回值的来源。

如果你想要重新定义终点箭头,可以通过复写getEndArrow方法来实现,后面也会有如何定义的示例demo。

代码:

getEndArrow(): h.JSX.Element | null {
    const { stroke, strokeWidth, offset, verticalLength } = this.getArrowStyle();
    return <path stroke={stroke} fill={stroke} strokeWidth={strokeWidth} transform="rotate(180)" d={`M 0 0 L ${offset} -${verticalLength} L ${offset} ${verticalLength} Z`} />;
  }
  

path绘制示意

image.png

getStartArrow

与终点箭头基本一致。

getArrowStyle

getArrowStyle方法通过整合model 主题等多个方面的数据,给出最终箭头的样式。如果你想要修改下箭头的颜色大小也可以通过复写这个方法实现。

源码:

getArrowStyle() {
    const { model, graphModel } = this.props;
    const edgeStyle = model.getEdgeStyle();
    const edgeAnimationStyle = model.getEdgeAnimationStyle();
    const { arrow } = graphModel.theme;
    const stroke = model.isAnimation ? edgeAnimationStyle.stroke : edgeStyle.stroke;
    return {
      ...edgeStyle,
      fill: stroke,
      stroke,
      ...arrow,
    } as ArrowStyle;
  }

复写:

class View extends PolylineEdge {
  getArrowStyle() {
    const style = super.getArrowStyle(); // 获取原来的返回值
    return {
      ...style,
      fill: 'green', // 只覆盖自己想要修改的,其他的保持不变,不用关心原来都有啥
      stroke: 'green',
    }
  }
}

自定义箭头

上面介绍了箭头实现的源码,如果想要自定义箭头,可以通过主题设置,自定义连线复写等方式,自定义箭头demo如下图所示。

image.png

demo线上地址:xinxin93.github.io/logicflow_a…

demo解读文章:juejin.cn/post/716391…

demo代码地址:github.com/xinxin93/lo…