基于react、svg和d3繪制流程圖

前言

??????因業(yè)務關系,新增繪制流程圖需求,最開始想使用第三方庫,調研后總有部分功能不能滿足,考慮后期需求變更的情況,決定自己研發(fā)。經多番調研,確認使用svg和d3繪制流程圖,并封裝為npm包。本文主要講解封裝庫的前期規(guī)劃、部分實現(xiàn)方式以及里面涉及到的一些好的idea。這是封裝好庫react-data-flow,并在不斷更新優(yōu)化中,目前的功能按照公司的需求定制的,后期新增自定義功能。

rdf.gif

前期規(guī)劃

首先確認需求,目前需要支持的需求有:

  1. 背景畫布,canvas;
  2. 繪制節(jié)點;
  3. 節(jié)點間拖拽繪制邊;
  4. 節(jié)點和節(jié)點間的連線規(guī)則;
  5. 節(jié)點選中效果;
  6. 節(jié)點選中后邊的動畫效果;
  7. 邊上的顯示文本及相關事件;
  8. 圖譜居中、縮放功能;

暫時梳理的需求有上面8項,分析需求,除了節(jié)點拖拽繪制邊及圖譜的居中和縮放功能,使用svg比較容易就能實現(xiàn)。需要考慮的是節(jié)點的事件、邊的事件以及圖譜上的拖拽已經縮放事件。d3已經有了成熟的方法能實現(xiàn),引入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);
    }
  }

獲取當前容器的寬高,除以間距,得到條數(shù)。到此,背景已繪制完成。

svg繪制節(jié)點

不清楚svg語法的點我,節(jié)點是一個矩形,需要故需要繪制矩形,svg繪制矩形根據(jù)左上角點的坐標和寬高繪制的,所以只需確定坐標即可。

代碼如下:

    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,繪制圖形挺簡單,多數(shù)的操作都是細節(jié)的調整。

svg繪制邊

語法:

命令 參數(shù) 說明
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個參數(shù),依次表示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)"
      />

多節(jié)點和多邊結合及相關事件的思路

上面簡單介紹了繪制點和繪制線條的方式,點與邊的結合確定坐標即可。

對于每個節(jié)點的事件而言,有兩種方法,一種是通過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-dnd、react-draggable等庫。

總結

本文只是簡單介紹了如何通過canvas、svg、d3繪制簡單的圖形,如果詳細講解上面封裝的庫,內容太多,不知從和開始介紹。若有疑問或建議歡迎評論區(qū)留言。

參考文獻

Canvas
SVG、
d3

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容