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

前言

??????因業(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)化中,目前的功能按照公司的需求定制的逮刨,后期新增自定義功能呕缭。

rdf.gif

前期規(guī)劃

首先確認(rèn)需求,目前需要支持的需求有:

  1. 背景畫布修己,canvas恢总;
  2. 繪制節(jié)點(diǎn);
  3. 節(jié)點(diǎn)間拖拽繪制邊睬愤;
  4. 節(jié)點(diǎn)和節(jié)點(diǎn)間的連線規(guī)則片仿;
  5. 節(jié)點(diǎn)選中效果;
  6. 節(jié)點(diǎn)選中后邊的動(dòng)畫效果尤辱;
  7. 邊上的顯示文本及相關(guān)事件砂豌;
  8. 圖譜居中厢岂、縮放功能;

暫時(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

繪制畫布

1610024667967.jpg

分解需求:

  1. 橫線和縱線(點(diǎn)線)舀锨,線與間隔[3, 3];
  2. 線與線間的間距為20;
  3. 繪制多條橫線和縱線即可宛逗;
    需求已很明確坎匿,繪制多條橫線和縱線組合起來(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-dndreact-draggable等庫(kù)淳蔼。

總結(jié)

本文只是簡(jiǎn)單介紹了如何通過(guò)canvas侧蘸、svg裁眯、d3繪制簡(jiǎn)單的圖形,如果詳細(xì)講解上面封裝的庫(kù)讳癌,內(nèi)容太多穿稳,不知從和開始介紹。若有疑問(wèn)或建議歡迎評(píng)論區(qū)留言晌坤。

參考文獻(xiàn)

Canvas逢艘、
SVG
d3

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骤菠,一起剝皮案震驚了整個(gè)濱河市它改,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌商乎,老刑警劉巖央拖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異截亦,居然都是意外死亡爬泥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門崩瓤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)袍啡,“玉大人,你說(shuō)我怎么就攤上這事却桶【呈洌” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵颖系,是天一觀的道長(zhǎng)嗅剖。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嘁扼,這世上最難降的妖魔是什么信粮? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮趁啸,結(jié)果婚禮上强缘,老公的妹妹穿的比我還像新娘。我一直安慰自己不傅,他們只是感情好旅掂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著访娶,像睡著了一般商虐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天秘车,我揣著相機(jī)與錄音典勇,去河邊找鬼。 笑死鲫尊,一個(gè)胖子當(dāng)著我的面吹牛痴柔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疫向,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼豪嚎!你這毒婦竟也來(lái)了搔驼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侈询,失蹤者是張志新(化名)和其女友劉穎舌涨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扔字,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囊嘉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了革为。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扭粱。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖震檩,靈堂內(nèi)的尸體忽然破棺而出琢蛤,到底是詐尸還是另有隱情,我是刑警寧澤抛虏,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布博其,位于F島的核電站,受9級(jí)特大地震影響迂猴,放射性物質(zhì)發(fā)生泄漏慕淡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一沸毁、第九天 我趴在偏房一處隱蔽的房頂上張望峰髓。 院中可真熱鬧,春花似錦以清、人聲如沸儿普。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)眉孩。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浪汪,已是汗流浹背巴柿。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留死遭,地道東北人广恢。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像呀潭,于是被迫代替她去往敵國(guó)和親钉迷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容