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

前期規(guī)劃
首先確認需求,目前需要支持的需求有:
- 背景畫布,canvas;
- 繪制節(jié)點;
- 節(jié)點間拖拽繪制邊;
- 節(jié)點和節(jié)點間的連線規(guī)則;
- 節(jié)點選中效果;
- 節(jié)點選中后邊的動畫效果;
- 邊上的顯示文本及相關事件;
- 圖譜居中、縮放功能;
暫時梳理的需求有上面8項,分析需求,除了節(jié)點拖拽繪制邊及圖譜的居中和縮放功能,使用svg比較容易就能實現(xiàn)。需要考慮的是節(jié)點的事件、邊的事件以及圖譜上的拖拽已經縮放事件。d3已經有了成熟的方法能實現(xiàn),引入d3,包的體積比較大,很多功能不能使用,考慮上面的需求,只需引入d3-zoom和d3-selection就可滿足。
到此可以確認使用的技術:
React、Canvas、SVG、d3-selection、d3-zoom
繪制畫布

分解需求:
- 橫線和縱線(點線),線與間隔[3, 3];
- 線與線間的間距為20;
- 繪制多條橫線和縱線即可;
需求已很明確,繪制多條橫線和縱線組合起來,背景就完成了。下面的繪制背景的方法是使用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ū)留言。