前言
設計師提供了一版樣式較復雜的流程圖,我搜了一些常用的vue-super-flow和vue-x6-flow等都只支持簡單的樣式。之前自己寫過純展示流程圖不涉及太多交互,感覺還是找一個成熟的插件開發(fā)更適合,也方便其他同事參考,所以最后選擇了用antv/G6自己個性化開發(fā),總結了使用antv/G6在vue項目中開發(fā)常見流程圖的過程。
示例如下:
一、什么是 G6?
G6官方文檔鏈接 https://g6.antv.vision/zh/docs/manual/introduction
G6 是一個圖可視化引擎。它提供了圖的繪制、布局、分析、交互、動畫等圖可視化的基礎能力。旨在讓關系變得透明,簡單。讓用戶獲得關系數據的 Insight。
基于 G6,用戶可以快速搭建自己的 圖分析 或 圖編輯 應用。
G6 作為一款專業(yè)的圖可視化引擎,具有以下特性:
優(yōu)秀的性能:支持大規(guī)模圖數據的交互與探索;
豐富的元素:內置豐富的節(jié)點與邊元素,自由配置,支持自定義;
可控的交互:內置 10+ 交互行為,支持自定義交互;
強大的布局:內置了 10+ 常用的圖布局,支持自定義布局;
便捷的組件:優(yōu)化內置組件功能及性能;
友好的體驗:根據用戶需求分層梳理文檔,支持 TypeScript 類型推斷。
除了默認好用、配置自由的內置功能,元素、交互、布局均具有高可擴展的自定義機制。
二、使用步驟
1.安裝依賴并導入
npm install --save @antv/g6
import G6 from '@antv/g6';
2.初始化數據
常用的主要是兩大類,一般圖和樹圖。
一般圖的數據結構是:
data:{
// 節(jié)點
nodes:[
{
id: 'XXX',
label: 'XXX',
key: 'XXX',
text: 'XXX',
img: require('XXX'),
},
...],
// 連接線
edges:[
{
source: '起始id',
target: '到達id',
},
...]
}
樹圖的數據結構是:
data:{
id:'XXX',
label: 'XXX',
children: [
{
id: 'XXX',
label: 'XXX',
children: [
{
id: 'XXX',
label: 'XXX',
children: []
},
...]
},
...],
}
我一開始選定了用樹圖實現,實現了樣式以后發(fā)現沒有辦法滿足產品要求的能夠隱藏/顯示不同層級并且重新渲染層級關系的功能,最后又更換成了一般圖的布局方式。
3.樹圖實現
(1)首先配置自定義節(jié)點
G6.registerNode(
'icon-node', {
options: {
size: [90, 50], // 設置節(jié)點大小
stroke: '#91d5ff', // 設置節(jié)點邊框顏色
fill: '#91d5ff', // 設置節(jié)點背景顏色
},
draw(cfg, group) {
const styles = this.getShapeStyle(cfg);
const {
labelCfg = {}
} = cfg;
const w = styles.width;
const h = styles.height;
// 我的項目里需要區(qū)分多個節(jié)點顯示不同的樣式,我用key來區(qū)分
if (cfg.key != "ACTION") {
const keyShape = group.addShape('rect', {
attrs: {
...styles,
x: -w / 2,
y: -h / 2,
},
});
/**
* leftIcon 格式如下:
* {
* style: ShapeStyle;
* img: ''
* }
*/
if (cfg.leftIcon) {
const {
style,
img
} = cfg.leftIcon;
// addShape是添加圖形,可以添加不同的屬性,具體配置可以看官方文檔
group.addShape('rect', {
attrs: {
x: 1 - w / 2,
y: 1 - h / 2,
width: 38,
height: styles.height - 2,
fill: '#8c8c8c',
...style,
},
});
group.addShape('image', {
attrs: {
x: 8 - w / 2,
y: 8 - h / 2,
width: 24,
height: 24,
img: img ||
'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png',
},
name: 'image-shape',
});
}
if (cfg.label) {
group.addShape('text', {
attrs: {
...labelCfg.style,
text: cfg.label,
x: 50 - w / 2,
y: 28 - h / 2,
},
});
}
return keyShape;
} else {
var keyShape = group.addShape('image', {
attrs: {
x: 8 - w / 2,
y: 8 - h / 2,
width: 24,
height: 24,
img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png',
},
name: 'image-shape',
});
group.addShape('text', {
attrs: {
fill: '#2f6bff',
fontSize: 24,
text: cfg.label,
x: 8 - w / 2,
y: 8 - h / 2,
},
});
group.addShape('text', {
attrs: {
fill: '#333333',
fontSize: 14,
text: '項目',
x: 8 - w / 2,
y: h - 4,
},
});
group.addShape('image', {
attrs: {
x: 40 - w / 2,
y: h - 20,
width: 14,
height: 14,
img: 'https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png',
},
});
return keyShape;
}
},
update: undefined,
// 自定義錨點,方便連線
getAnchorPoints: () => {
return [
[0, 0.5], // 左側中間
[1, 0.5], // 右側中間
];
},
},
'single-shape',
);
(2)自定義帶箭頭的貝塞爾曲線(連接線)
G6.registerEdge('flow-line', {
draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const {
style
} = cfg;
const shape = group.addShape('path', {
attrs: {
stroke: style.stroke,
// endArrow: style.endArrow, // 需要箭頭可以自己配置箭頭 有開始箭頭和結束箭頭
path: [ // 用path路徑配置帶折點的連接線 M表示開始的坐標,L表示拐點的坐標
['M', startPoint.x, startPoint.y],
['L', (startPoint.x + endPoint.x) / 2, (startPoint.y)],
['L', (startPoint.x + endPoint.x) / 2, endPoint.y],
['L', endPoint.x, endPoint.y],
],
},
});
return shape;
},
afterDraw(cfg, group) {
const shape = group.get('children')[0];
const startPoint = shape.getPoint(0);
//創(chuàng)建節(jié)點之間的圓圈,并為每一個設置樣式
const circle = group.addShape('circle', {
attrs: {
x: startPoint.x + 92,
y: startPoint.y,
fill: '#cbdaff',
r: 4, //圓圈大小
},
name: 'circle-shape',
});
// 實現動態(tài)效果,從線的開始滑到結束
circle.animate(
ratio => {
const tmpPoint = shape.getPoint(ratio);
return {
x: tmpPoint.x,
y: tmpPoint.y,
};
}, {
repeat: 1, //動畫是否重復
duration: 3000, //一次動畫持續(xù)時長
},
);
},
}, );
(3)設置默認樣式
// 設置默認開啟狀態(tài)樣式
const defaultStateStyles = {
hover: {
stroke: '#2f6bff',
},
};
// 設置默認節(jié)點樣式
const defaultNodeStyle = {
fill: '#fff',
stroke: '#cbdaff',
radius: 5,
shadowColor: '#cbdaff', // 陰影顏色
shadowBlur: 10, // 陰影范圍
shadowOffsetX: 0, // 陰影偏移位置
shadowOffsetY: 5,
cursor: 'pointer', // 鼠標移動到節(jié)點上的樣式
};
// 設置默認鏈接線樣式
const defaultEdgeStyle = {
stroke: '#cbdaff',
// 結尾箭頭樣式
endArrow: {
path: 'M 0,0 L 12, 6 L 9,0 L 12, -6 Z',
fill: '#91d5ff',
d: -60, // x軸上的偏移量
},
};
// 設置默認節(jié)點名稱樣式
const defaultLabelCfg = {
style: {
fill: '#333',
fontSize: 14,
},
};
(4)配置樹圖并渲染
const graph = new G6.TreeGraph({
container: 'html元素的id',
width: XXX, // 渲染的寬度
height: XXX, // 渲染的高度
linkCenter: true,
plugins: [minimap], // 配置插件
modes: {
default: ['drag-canvas', 'zoom-canvas'], // 常用的拖拽和縮放
},
defaultNode: {
type: 'icon-node', // 自定義節(jié)點名稱,記得保持一致
size: [120, 40], //設置節(jié)點大小
style: defaultNodeStyle, // 配置過的默認樣式,直接寫在這里也可以
labelCfg: defaultLabelCfg, // 配置過的默認樣式,直接寫在這里也可以
},
defaultEdge: {
type: 'flow-line', // 自定義連接線名稱,記得保持一致
style: defaultEdgeStyle, // 配置過的默認樣式,直接寫在這里也可以
},
nodeStateStyles: defaultStateStyles, // 配置過的默認樣式,直接寫在這里也可以
edgeStateStyles: defaultStateStyles, // 配置過的默認樣式,直接寫在這里也可以
// 若數據中不存在節(jié)點位置,則默認為隨機布局。配置布局類型及其參數。
layout: {
// 類型 總共三種:徑向:radial 有向分層:dagre 力導:force
type: 'dagre',
rankdir: 'LR', // 可選值:'TB' | 'BT' | 'LR' | 'RL',默認為圖的中心 TB T,B,L,R 代表top,bottom,left,right
},
});
graph.data(data); // 配置數據
graph.render(); // 渲染流程圖
graph.fitView(); // 讓畫布內容適應視口,可選參數padding, rules, animate, animateCfg
(6)小地圖插件
// 設置出現一個縮略地圖
const minimap = new G6.Minimap({
size: [150, 100],
});
4.一般圖實現
(1)自定義節(jié)點
一般圖中獲取節(jié)點屬性獲取不到節(jié)點的寬高,所以自己設置一個w、h,然后再剛剛的基礎上加入一些細節(jié)
G6.registerNode(
// 該新節(jié)點類型名稱
'node',
// 該新節(jié)點類型的定義
// 當有 extendedTypeName (第三個參數)時,沒被復寫的函數將會繼承 extendedTypeName 的定義
{
// cfg 節(jié)點身上所有的配置:包括label,size,x,y坐標等
// 這個方法,每渲染一個節(jié)點,執(zhí)行一次
drawShape(cfg, group) {
const styles = this.getShapeStyle(cfg);
// ctg上的id key label 都可以決定當前節(jié)點的類型
// 可以根據標題長度設置節(jié)點的大小
// cfg.size = (cfg.label.length / 10) * 130;
// if (cfg.label.length / 10 < 1) {
// widthX = 120;
// } else {
// widthX = cfg.size;
// }
let w = 180,
h = 50;
// 根據設計圖第一個節(jié)點主要是展示圖片和個數,配置對應的形狀
if (cfg.key == "0") {
w = 100;
h = 155;
var keyShape = group.addShape('image', {
attrs: {
x: -w / 2,
y: -h / 2,
width: w,
height: h,
img: cfg.img,
},
name: 'image-shape',
});
group.addShape('circle', {
attrs: {
x: w / 2,
y: 0,
r: 2.5,
fill: '#fff',
stroke: '#cbdaff',
lineWidth: 2,
},
});
group.addShape('text', {
attrs: {
fill: '#2f6bff',
fontSize: 24,
text: cfg.label,
textAlign: 'center',
x: 0,
y: -45,
},
});
group.addShape('text', {
attrs: {
fill: '#333333',
fontSize: 14,
text: '項目',
textAlign: 'center',
x: 0,
y: h / 2 - 30,
},
});
return keyShape;
} else {
// 根據標題的長度判斷節(jié)點的高度
var rowNum = 0
for (let i = 0; i < cfg.label.length; i++) {
var j = i + rowNum
if (i % 8 == 0 && i > 0 && j != cfg.label.length) {
var newStr = cfg.label.slice(0, j) + "\n" + cfg.label.slice(j);
cfg.label = newStr
rowNum++;
}
}
if (rowNum > 1) {
h += (rowNum - 1) * 15
}
cfg.size = [w, h]
const rect = group.addShape('rect', {
attrs: {
x: -w / 2,
y: -h / 2,
width: w,
height: h,
fill: '#fff',
stroke: '#a0d4ff',
radius: 9,
shadowColor: '#a0d4ff', // 陰影顏色
shadowBlur: 10, // 陰影范圍
shadowOffsetX: 0, // 陰影偏移位置
shadowOffsetY: 5,
cursor: 'pointer', // 鼠標移動 到節(jié)點上的樣式
},
});
group.addShape('circle', {
attrs: {
x: 0 - w / 2,
y: 0,
r: 2.5,
fill: '#fff',
stroke: '#a0d4ff',
lineWidth: 2,
},
});
group.addShape('image', {
attrs: {
x: cfg.key != '1' ? 17 - w / 2 : 12 - w / 2,
y: cfg.key != '1' ? -15 : -19,
width: cfg.key != '1' ? 30 : 38,
height: cfg.key != '1' ? 30 : 38,
img: cfg.img,
},
});
group.addShape('text', {
attrs: {
x: 60 - w / 2,
y: 0,
fill: '#333333',
fontSize: 14,
text: cfg.label,
textBaseline: 'middle',
},
});
return rect;
}
},
/**
* 獲取錨點(相關邊的連入點)
* @param {Object} cfg 節(jié)點的配置項
* @return {Array|null} 錨點(相關邊的連入點)的數組,如果為 null,則沒有控制點
*/
// 自定義錨點
getAnchorPoints: () => {
return [
[0, 0.5], // 左側中間
[1, 0.5], // 右側中間
];
},
},
// 被繼承的節(jié)點類型,可以是內置節(jié)點類型名,也可以是其他自定義節(jié)點的類型名。
// extendedTypeName 未指定時代表不繼承其他類型的節(jié)點;
// 例如基類 'single-node',或 'circle', 'rect' 等
'single-shape'
);
連接線跟上面樹圖的配置一樣即可,如果想要曲線可以自己修改
(2)配置圖屬性并渲染
const graph = new G6.Graph({
// 常用配置項
// 類型:Boolean;默認:'false'。圖是否自適應畫布。
fitView: true,
// 類型:Number | Array;默認:0。圖自適應畫布時的四周留白像素值。fitView 為 true 時生效。
// fitViewPadding : 0
// 類型:Boolean;默認:'false'。是否平移圖使其中心對齊到畫布中心。v3.5.1 后支持。
fitCenter: true,
// bloodView:流程圖容器id
container: 'projectView',
width:'XXX',
height:'XXX',
//modes 交互行為相關
// 配置多種交互模式及其包含的交互事件的。
modes: {
default: [
'drag-canvas',
'zoom-canvas',
// 如果沒有特殊的樣式可以直接用內置的tooltip
// {
// type: 'tooltip',
// formatText(model) {
// console.log(model)
// const cfg = model.text;
// const text = model.text;
// // cfg.forEach((row) => {
// // text.push(row.label + ':' + row.value + '<br>');
// // });
// return text;
// },
// offsetX: 70,
// offsetY: 20,
// },
],
},
plugins: [tooltip], // 這次項目懸浮框用了插件
zoom: 1,
// 若數據中不存在節(jié)點位置,則默認為隨機布局。配置布局類型及其參數。
layout: {
// 類型 總共三種:徑向:radial 有向分層:dagre 力導:force
type: 'dagre',
// 'LR':從左至右布局;
rankdir: 'LR', // 可選可選值:'TB' | 'BT' | 'LR' | 'RL',默認為圖的中心 TB
nodesep: 20, //節(jié)點之間的距離,水平布局時指垂直距離
ranksep: 40, //層級之間的距離,水平布局時指垂直距離
},
// defaultNode類型:Object。默認情況下全局節(jié)點的配置項,包括樣式屬性和其他屬性
// G6 的內置節(jié)點包括
// circle圓形,rect長方形,ellipse橢圓,diamond菱形,triangle三角形,
// star五角星,image圖片,modelRect卡片,donut圓形(v4.2.5 起支持)。
defaultNode: {
type: 'node', // 這里的type指向自定義節(jié)點
// size:300,
size: [180, 50],
style: defaultNodeStyle,
labelCfg: defaultLabelCfg,
},
// defaultEdge 類型:Object。默認情況下全局邊的配置項,包括樣式屬性和其他屬性
defaultEdge: {
type: 'flow-line',
style: {
endArrow: true,
lineWidth: 2,
stroke: '#cbdaff',
},
},
nodeStateStyles: defaultStateStyles,
edgeStateStyles: defaultStateStyles,
});
// 動態(tài)配置完層級后需要銷毀后重新渲染,否則頁面上會顯示多個渲染的流程圖
if (this.graph != null) {
this.graph.destroy();
}
this.graph = graph
graph.data(data);
// 渲染
graph.render();
// this.graph.zoomTo(1); // 縮放 參數為縮放的倍數,后面也可跟x,y坐標,將以傳入坐標為中心
// this.graph.moveTo(0,height/2); // 采用絕對位移將畫布移動到指定坐標
(3)自定義tooltip
需求中的懸浮框需要展示該節(jié)點下面的所有項目及帶有查看詳情按鈕,所以我使用了插件沒有用內置的。
需要注意的一點是在getContent()里的代碼一定要嚴格遵守編寫規(guī)范,尤其注意后面一定要帶';',否則會一直報錯而且你找不到原因?。。?/code>
const tooltip = new G6.Tooltip({
offsetX: -400, // 偏移距離
offsetY: -850,
trigger: 'click', // 觸發(fā)方式 可選 mouseenter,
getContent(e) {
const outDiv = document.createElement('div');
outDiv.style.width = '270px';
outDiv.style.padding = '0 10px';
let ulText = ``;
if (typeof (e.item.getModel().text) == 'object') {
(e.item.getModel().text).map((item, index) => {
ulText +=
`<li style='padding:5px 60px 5px 0;
position: relative;word-break: break-all;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;font-size: 12px;'>項目名稱:<span style="color:#333">${item.label}</span><span type="text" style="color: #2f6bff;border:none;background:transparent;cursor:pointer; position: absolute;top: 50%;right: 0;transform: translateY(-50%);">查看詳情</span></li>`;
});
outDiv.innerHTML =
`<div style="font-size:14px;color:#333;font-weight:bold;">${e.item.getModel().label}</div><ul style="margin:10px 0 0;padding:0;font-size:12px">` +
ulText +
`</ul>`;
} else {
outDiv.innerHTML =
`<div style="font-size:14px;color:#333;font-weight:bold;">${e.item.getModel().label}</div>`;
}
return outDiv
},
itemTypes: ['node']
});
5.插件
G6 提供了一些可插拔的組件,包括:
Legend (v4.3.0 起支持)
SnapLine (v4.3.0 起支持)
Grid
Minimap
ImageMinimap
Edge Bundling
Menu
ToolBar
TimeBar
Tooltip
Fisheye
EdgeFilterLens
三、注意事項
如何讓 IE 支持 G6
對于這類問題,我們在項目中只需要引入 babel-polyfill 即可,具體使用方法如下:
在主入門文件中引入 babel-polyfill ;
在 bable-loader 中加入如下代碼:
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('node_modules/@antv/g6')]
}
include 表示哪些目錄中的 .js 文件需要進行 babel-loader;exclude 表示哪些目錄中的 .js 文件不要進行 babel-loader。文章來源:http://www.zghlxwxcb.cn/news/detail-408666.html
總結
以上就是今天要講的內容,本文僅僅簡單介紹了antv/G6的使用,更多的示例及API請移步官方文檔。文章來源地址http://www.zghlxwxcb.cn/news/detail-408666.html
到了這里,關于使用antv/G6在vue項目中開發(fā)較復雜樣式流程圖的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!