前言
??????因業(yè)務(wù)關(guān)系,新增繪制流程圖需求蛆挫,最開始想使用第三方庫(kù)赃承,調(diào)研后總有部分功能不能滿足,考慮后期需求變更的情況璃吧,決定自己研發(fā)楣导。經(jīng)多番調(diào)研,確認(rèn)使用svg和d3繪制流程圖畜挨,并封裝為npm包筒繁。本文主要講解封裝庫(kù)的前期規(guī)劃、部分實(shí)現(xiàn)方式以及里面涉及到的一些好的idea巴元。這是封裝好庫(kù)react-data-flow毡咏,并在不斷更新優(yōu)化中,目前的功能按照公司的需求定制的逮刨,后期新增自定義功能呕缭。
前期規(guī)劃
首先確認(rèn)需求,目前需要支持的需求有:
- 背景畫布修己,canvas恢总;
- 繪制節(jié)點(diǎn);
- 節(jié)點(diǎn)間拖拽繪制邊睬愤;
- 節(jié)點(diǎn)和節(jié)點(diǎn)間的連線規(guī)則片仿;
- 節(jié)點(diǎn)選中效果;
- 節(jié)點(diǎn)選中后邊的動(dòng)畫效果尤辱;
- 邊上的顯示文本及相關(guān)事件砂豌;
- 圖譜居中厢岂、縮放功能;
暫時(shí)梳理的需求有上面8項(xiàng)阳距,分析需求塔粒,除了節(jié)點(diǎn)拖拽繪制邊及圖譜的居中和縮放功能,使用svg比較容易就能實(shí)現(xiàn)筐摘。需要考慮的是節(jié)點(diǎn)的事件卒茬、邊的事件以及圖譜上的拖拽已經(jīng)縮放事件。d3已經(jīng)有了成熟的方法能實(shí)現(xiàn)蓄拣,引入d3扬虚,包的體積比較大,很多功能不能使用球恤,考慮上面的需求辜昵,只需引入d3-zoom和d3-selection就可滿足。
到此可以確認(rèn)使用的技術(shù):
React咽斧、Canvas堪置、SVG、d3-selection张惹、d3-zoom
繪制畫布
分解需求:
- 橫線和縱線(點(diǎn)線)舀锨,線與間隔[3, 3];
- 線與線間的間距為20;
- 繪制多條橫線和縱線即可宛逗;
需求已很明確坎匿,繪制多條橫線和縱線組合起來(lái),背景就完成了雷激。下面的繪制背景的方法是使用Canvas繪制替蔬,不清楚Canvas繪制線條語(yǔ)法,點(diǎn)我屎暇。
繪制線條封裝:
// 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();
}
}
線條繪制方法已經(jīng)繪制好承桥,只需要傳入起點(diǎn)和終點(diǎn)左邊即可;
接下來(lái)的工作根悼,需要繪制多條線條凶异。方法如下:
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);
}
}
獲取當(dāng)前容器的寬高,除以間距挤巡,得到條數(shù)剩彬。到此,背景已繪制完成矿卑。
svg繪制節(jié)點(diǎn)
不清楚svg語(yǔ)法的點(diǎn)我,節(jié)點(diǎn)是一個(gè)矩形襟衰,需要故需要繪制矩形,svg繪制矩形根據(jù)左上角點(diǎn)的坐標(biāo)和寬高繪制的粪摘,所以只需確定坐標(biāo)即可瀑晒。
代碼如下:
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,繪制圖形挺簡(jiǎn)單徘意,多數(shù)的操作都是細(xì)節(jié)的調(diào)整苔悦。
svg繪制邊
語(yǔ)法:
命令 | 參數(shù) | 說(shuō)明 |
---|---|---|
M m | x y | 移動(dòng)畫筆到制定坐標(biāo) |
L l | x y | 繪制一條到給定坐標(biāo)的線 |
H h | x | 繪制一條到給定x坐標(biāo)的橫線 |
V v | y | 繪制一條到給定y坐標(biāo)的垂線 |
A a | rx ry x-axis-rotation large-arc sweep x y | 圓弧曲線命令有7個(gè)參數(shù),依次表示x方向半徑椎咧、y方向半徑玖详、旋轉(zhuǎn)角度、大圓標(biāo)識(shí)勤讽、順逆時(shí)針標(biāo)識(shí)蟋座、目標(biāo)點(diǎn)x、目標(biāo)點(diǎn)y脚牍。大圓標(biāo)識(shí)和順逆時(shí)針以0和1表示向臀。0表示小圓、逆時(shí)針 |
Q q | x1 y1 x y | 繪制一條從當(dāng)前點(diǎn)到x,y控制點(diǎn)為x1,y1的二次貝塞爾曲線 |
T t | x y | 繪制一條從當(dāng)前點(diǎn)到x,y的光滑二次貝塞爾曲線诸狭,控制點(diǎn)為前一個(gè)Q命令的控制點(diǎn)的中心對(duì)稱點(diǎn)券膀,如果沒(méi)有前一條則已當(dāng)前點(diǎn)為控制點(diǎn)。 |
C c | x1 y1 x2 y2 x y | 繪制一條從當(dāng)前點(diǎn)到x,y控制點(diǎn)為x1,y1 x2,y2的三次貝塞爾曲線 |
S s | x2 y2 x y | 繪制一條從當(dāng)前點(diǎn)到x,y的光滑三次貝塞爾曲線驯遇。第一個(gè)控制點(diǎn)為前一個(gè)C命令的第二個(gè)控制點(diǎn)的中心對(duì)稱點(diǎn)芹彬,如果沒(méi)有前一條曲線,則第一個(gè)控制點(diǎn)為當(dāng)前的點(diǎn)叉庐。 |
代碼:
<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é)點(diǎn)和多邊結(jié)合及相關(guān)事件的思路
上面簡(jiǎn)單介紹了繪制點(diǎn)和繪制線條的方式舒帮,點(diǎn)與邊的結(jié)合確定坐標(biāo)即可。
對(duì)于每個(gè)節(jié)點(diǎn)的事件而言陡叠,有兩種方法玩郊,一種是通過(guò)d3-selection獲取元素demo,從而操作demo匾竿,如下:
d3.select(svgElement).on('click', function(){})
第二種方法瓦宜,React的方式,在元素上綁定onClick事件岭妖。如下:
<g onClick={handleClick}>
<rect />
<text />
</g>
對(duì)于拖拽事件而言临庇,同樣事兩種方法,一種事d3提供的方法昵慌,推薦這種假夺,如下:
d3.select(svgElement).call(d3.drag().on('drag', function() {}))
另外一種方式,在react元素上綁定拖拽事件斋攀,需要借助第三方庫(kù)已卷,如react-dnd
、react-draggable
等庫(kù)淳蔼。
總結(jié)
本文只是簡(jiǎn)單介紹了如何通過(guò)canvas侧蘸、svg裁眯、d3繪制簡(jiǎn)單的圖形,如果詳細(xì)講解上面封裝的庫(kù)讳癌,內(nèi)容太多穿稳,不知從和開始介紹。若有疑問(wèn)或建議歡迎評(píng)論區(qū)留言晌坤。