基于react、svg和d3绘制流程图

前言

      因业务关系,新增绘制流程图需求,最开始想使用第三方库,调研后总有部分功能不能满足,考虑后期需求变更的情况,决定自己研发。经多番调研,确认使用svg和d3绘制流程图,并封装为npm包。本文主要讲解封装库的前期规划、部分实现方式以及里面涉及到的一些好的idea。这是封装好库react-data-flow,并在不断更新优化中,目前的功能按照公司的需求定制的,后期新增自定义功能。

rdf.gif

前期规划

首先确认需求,目前需要支持的需求有:

  1. 背景画布,canvas;
  2. 绘制节点;
  3. 节点间拖拽绘制边;
  4. 节点和节点间的连线规则;
  5. 节点选中效果;
  6. 节点选中后边的动画效果;
  7. 边上的显示文本及相关事件;
  8. 图谱居中、缩放功能;

暂时梳理的需求有上面8项,分析需求,除了节点拖拽绘制边及图谱的居中和缩放功能,使用svg比较容易就能实现。需要考虑的是节点的事件、边的事件以及图谱上的拖拽已经缩放事件。d3已经有了成熟的方法能实现,引入d3,包的体积比较大,很多功能不能使用,考虑上面的需求,只需引入d3-zoom和d3-selection就可满足。

到此可以确认使用的技术:

React、Canvas、SVG、d3-selection、d3-zoom

绘制画布

1610024667967.jpg

分解需求:

  1. 横线和纵线(点线),线与间隔[3, 3];
  2. 线与线间的间距为20;
  3. 绘制多条横线和纵线即可;
    需求已很明确,绘制多条横线和纵线组合起来,背景就完成了。下面的绘制背景的方法是使用Canvas绘制,不清楚Canvas绘制线条语法,点我

绘制线条封装:

   // canvas的ref
   const girdRef = useRef<HTMLCanvasElement>(null);
   const canvasWidth = 1920 * 2;
   const canvasHeight = 1080 * 2;
   const grid = {
        strokeColor: '#E2E2F0',
        strokeWidth: 1,
        distance: 20,
        isLineDash: true,
        lineDash: [3, 3],
      }

  const drawGridLine = (x1: number, y1: number, x2: number, y2: number) => {
      if (girdRef && girdRef.current) {
        const gridCanvas: any = girdRef.current.getContext('2d');
        const { strokeWidth, strokeColor, lineDash } = grid;

        gridCanvas.beginPath();
        gridCanvas.moveTo(x1, y1);
        gridCanvas.lineTo(x2, y2);
        gridCanvas.setLineDash(lineDash);
        gridCanvas.lineWidth = strokeWidth;
        gridCanvas.strokeStyle = strokeColor;
        gridCanvas.stroke();
      }
    }

线条绘制方法已经绘制好,只需要传入起点和终点左边即可;

接下来的工作,需要绘制多条线条。方法如下:

  const drawGrid = () => {
    const distance = grid.distance;
    const rowNumber = Math.ceil(canvasHeight / distance);
    const colNumber = Math.ceil(canvasWidth / distance);

    for (let i = 0; i < rowNumber; i++) {
      drawGridLine(0, i * distance, canvasWidth, i * distance);
    }

    for (let j = 0; j < colNumber; j++) {
      drawGridLine(j * distance, 0, j * distance, canvasHeight);
    }
  }

获取当前容器的宽高,除以间距,得到条数。到此,背景已绘制完成。

svg绘制节点

不清楚svg语法的点我,节点是一个矩形,需要故需要绘制矩形,svg绘制矩形根据左上角点的坐标和宽高绘制的,所以只需确定坐标即可。

代码如下:

    const position = {
      x: 300, y: 300
    }
   const rect = {
    strokeWidth: 1,
    strokeColor: '#2994FF',
    fill: '#FAFBFC',
    width: 180,
    height: 50,
    distance: 10,
    radius: 4,
    hover: {
      fill: '#E9F3FC',
    },
    delRadius: 10,
  }

   <g>
      <rect
        className="rectNode"
        x={position.x - width / 2}
        y={position.y - height / 2}
        rx={radius}
        ry={radius}
        width={width}
        height={height}
        stroke={strokeColor}
        fill={rectFill}
      />
      <text
        className="rectTextNode"
        id={`text_id_${node.id}`}
        x={position.x}
        y={position.y + rectText.marginTop}
        fill={rectText.fill}
        style={{ textAnchor: 'middle', fontSize: rectText.fontSize, userSelect: 'none' }}>
        {title}
      </text>
    </g>

只需要知道api,绘制图形挺简单,多数的操作都是细节的调整。

svg绘制边

语法:

命令 参数 说明
M m x y 移动画笔到制定坐标
L l x y 绘制一条到给定坐标的线
H h x 绘制一条到给定x坐标的横线
V v y 绘制一条到给定y坐标的垂线
A a rx ry x-axis-rotation large-arc sweep x y 圆弧曲线命令有7个参数,依次表示x方向半径、y方向半径、旋转角度、大圆标识、顺逆时针标识、目标点x、目标点y。大圆标识和顺逆时针以0和1表示。0表示小圆、逆时针
Q q x1 y1 x y 绘制一条从当前点到x,y控制点为x1,y1的二次贝塞尔曲线
T t x y 绘制一条从当前点到x,y的光滑二次贝塞尔曲线,控制点为前一个Q命令的控制点的中心对称点,如果没有前一条则已当前点为控制点。
C c x1 y1 x2 y2 x y 绘制一条从当前点到x,y控制点为x1,y1 x2,y2的三次贝塞尔曲线
S s x2 y2 x y 绘制一条从当前点到x,y的光滑三次贝塞尔曲线。第一个控制点为前一个C命令的第二个控制点的中心对称点,如果没有前一条曲线,则第一个控制点为当前的点。

代码:

<path
        d={` M 275, 231
                  L 315, 231
                  L 341.5, 231
                  L 341.5, 231
                  L 368, 231
                  L 408, 231`}
        strokeWidth="1"
        stroke="#ccc"
        fill="none"
        markerEnd="url(#arrow)"
      />

多节点和多边结合及相关事件的思路

上面简单介绍了绘制点和绘制线条的方式,点与边的结合确定坐标即可。

对于每个节点的事件而言,有两种方法,一种是通过d3-selection获取元素demo,从而操作demo,如下:

d3.select(svgElement).on('click', function(){})

第二种方法,React的方式,在元素上绑定onClick事件。如下:

<g onClick={handleClick}>
  <rect />
  <text />
</g>

对于拖拽事件而言,同样事两种方法,一种事d3提供的方法,推荐这种,如下:

d3.select(svgElement).call(d3.drag().on('drag', function() {}))

另外一种方式,在react元素上绑定拖拽事件,需要借助第三方库,如react-dndreact-draggable等库。

总结

本文只是简单介绍了如何通过canvas、svg、d3绘制简单的图形,如果详细讲解上面封装的库,内容太多,不知从和开始介绍。若有疑问或建议欢迎评论区留言。

参考文献

Canvas
SVG
d3

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容