G6 封裝了一系列交互方法,方便用戶直接使用。本文將爲 Tutorial 案例 增加簡單的交互:hover 節點、點擊節點、點擊邊、放縮畫布、拖拽畫布。本節目標效果如下:
基本概念
交互行爲 Behavior
G6 中的交互行爲。G6 內置了一系列交互行爲,用戶可以直接使用。簡單地理解,就是可以一鍵開啓這些交互行爲:
- drag-canvas:拖拽畫布;
- zoom-canvas:縮放畫布。
交互管理 Mode
Mode 是 G6 交互行爲的管理機制,一個 mode 是多種行爲 Behavior 的組合,允許用戶通過切換不同的模式進行交互行爲的管理。
交互狀態 State
狀態 State 是 G6 中的狀態機制。用戶可以爲圖中的元素(節點/邊)設置不同的狀態及不同狀態下的樣式。在狀態發生變化時,G6 自動更新元素的樣式。例如,可以爲節點設置狀態 ‘click’ 爲 true 或 false,併爲節點設置 ‘click’ 的樣式爲加粗節點邊框。當 ‘click’ 狀態被切換爲 true 時,節點的邊框將會被加粗,‘click’ 狀態被切換爲 false 時,節點的樣式恢復到默認。
使用方法
拖拽、縮放——內置的交互行爲
在 G6 中使用內置 Behavior 的方式非常簡單,只需要在圖實例化時配置 modes。
拖拽和縮放屬於 G6 內置交互行爲,修改代碼如下:
const graph = new G6.Graph({
// ... // 其他配置項
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允許拖拽畫布、放縮畫布、拖拽節點
},
});
除了直接使用內置交互名稱外,也可以爲 Behavior 配置參數,例如放縮畫布的敏感度、最大/最小放縮程度等。
上面代碼中的 modes 定義了 G6 的模式,default 是默認的模式,還可以允許有其他的模式,比如:編輯模式 edit 等。不同的模式,用戶能進行的行爲可以不同,比如默認模式能拖拽畫布,編輯模式不允許拖拽畫布:
// 舉例解釋不同模式
modes: {
default: ['drag-canvas'],
edit: []
}
Hover、Click 改變樣式——狀態式交互
有時我們希望通過交互可以將元素樣式變成特定樣式,如我們看到的圖 1 中,鼠標 hover 節點、點擊節點、點擊邊時,樣式發生了變化。這裏涉及到了 G6 中 狀態 State 的概念。簡單地說,是否 hover 、click 、任何操作(可以是自己起的狀態名),都可以稱爲一種狀態(state)。用戶可以自由設置不同狀態下的元素樣式。要達到交互更改元素樣式,需要兩步:
- 設置各狀態下的元素樣式;
- 監聽事件並切換元素狀態。
設置各狀態下的元素樣式
在實例化圖時,通過 nodeStateStyles 和 edgeStateStyles 兩個配置項可以配置元素在不同狀態下的樣式。
爲達到 Tutorial 案例 中的效果:
- 鼠標 hover 節點時,該節點顏色變淺;
- 點擊節點時,該節點邊框加粗變黑;
- 點擊邊時,該邊變成藍色。
下面代碼設置了節點分別在 hover 和 click 狀態爲 true 時的樣式,邊在 click 狀態爲 true 時的樣式:
const graph = new G6.Graph({
// ... // 其他配置項
// 節點不同狀態下的樣式集合
nodeStateStyles: {
// 鼠標 hover 上節點,即 hover 狀態爲 true 時的樣式
hover: {
fill: 'lightsteelblue',
},
// 鼠標點擊節點,即 click 狀態爲 true 時的樣式
click: {
stroke: '#000',
lineWidth: 3,
},
},
// 邊不同狀態下的樣式集合
edgeStateStyles: {
// 鼠標點擊邊,即 click 狀態爲 true 時的樣式
click: {
stroke: 'steelblue',
},
},
});
監聽事件並切換元素狀態
G6 中所有元素監聽都掛載在圖實例上,如下代碼中的 graph 對象是 G6.Graph 的實例,graph.on() 函數監聽了某元素類型(node / edge)的某種事件。
// 在圖實例 graph 上監聽
graph.on('元素類型:事件名', e => {
// do something
});
現在,我們通過下面代碼,爲 Tutorial 案例 增加點和邊上的監聽事件,並在監聽函數裏使用 graph.setItemState() 改變元素的狀態:
// 鼠標進入節點
graph.on('node:mouseenter', e => {
const nodeItem = e.item; // 獲取鼠標進入的節點元素對象
graph.setItemState(nodeItem, 'hover', true); // 設置當前節點的 hover 狀態爲 true
});
// 鼠標離開節點
graph.on('node:mouseleave', e => {
const nodeItem = e.item; // 獲取鼠標離開的節點元素對象
graph.setItemState(nodeItem, 'hover', false); // 設置當前節點的 hover 狀態爲 false
});
// 點擊節點
graph.on('node:click', e => {
// 先將所有當前是 click 狀態的節點置爲非 click 狀態
const clickNodes = graph.findAllByState('node', 'click');
clickNodes.forEach(cn => {
graph.setItemState(cn, 'click', false);
});
const nodeItem = e.item; // 獲取被點擊的節點元素對象
graph.setItemState(nodeItem, 'click', true); // 設置當前節點的 click 狀態爲 true
});
// 點擊邊
graph.on('edge:click', e => {
// 先將所有當前是 click 狀態的邊置爲非 click 狀態
const clickEdges = graph.findAllByState('edge', 'click');
clickEdges.forEach(ce => {
graph.setItemState(ce, 'click', false);
});
const edgeItem = e.item; // 獲取被點擊的邊元素對象
graph.setItemState(edgeItem, 'click', true); // 設置當前邊的 click 狀態爲 true
});
完整代碼
至此,完整代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tutorial Demo</title>
</head>
<body>
<div id="mountNode"></div>
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.0/build/g6.js"></script>
<script>
const graph = new G6.Graph({
container: 'mountNode',
width: 800,
height: 600,
// 節點默認配置
defaultNode: {
labelCfg: {
style: {
fill: '#fff',
},
},
},
// 邊默認配置
defaultEdge: {
labelCfg: {
autoRotate: true,
},
},
// 節點在各狀態下的樣式
nodeStateStyles: {
// hover 狀態爲 true 時的樣式
hover: {
fill: 'lightsteelblue',
},
// click 狀態爲 true 時的樣式
click: {
stroke: '#000',
lineWidth: 3,
},
},
// 邊在各狀態下的樣式
edgeStateStyles: {
// click 狀態爲 true 時的樣式
click: {
stroke: 'steelblue',
},
},
// 佈局
layout: {
type: 'force',
linkDistance: 200,
preventOverlap: true,
nodeStrength: -30,
edgeStrength: 0.1,
},
// 內置交互
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
},
});
const main = async () => {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json',
);
const remoteData = await response.json();
const nodes = remoteData.nodes;
const edges = remoteData.edges;
nodes.forEach(node => {
if (!node.style) {
node.style = {};
}
node.style.lineWidth = 1;
node.style.stroke = '#666';
node.style.fill = 'steelblue';
switch (node.class) {
case 'c0': {
node.shape = 'circle';
node.size = 30;
break;
}
case 'c1': {
node.shape = 'rect';
node.size = [35, 20];
break;
}
case 'c2': {
node.shape = 'ellipse';
node.size = [35, 20];
break;
}
}
});
edges.forEach(edge => {
if (!edge.style) {
edge.style = {};
}
edge.style.lineWidth = edge.weight;
edge.style.opacity = 0.6;
edge.style.stroke = 'grey';
});
graph.data(remoteData);
graph.render();
// 監聽鼠標進入節點
graph.on('node:mouseenter', e => {
const nodeItem = e.item;
// 設置目標節點的 hover 狀態 爲 true
graph.setItemState(nodeItem, 'hover', true);
});
// 監聽鼠標離開節點
graph.on('node:mouseleave', e => {
const nodeItem = e.item;
// 設置目標節點的 hover 狀態 false
graph.setItemState(nodeItem, 'hover', false);
});
// 監聽鼠標點擊節點
graph.on('node:click', e => {
// 先將所有當前有 click 狀態的節點的 click 狀態置爲 false
const clickNodes = graph.findAllByState('node', 'click');
clickNodes.forEach(cn => {
graph.setItemState(cn, 'click', false);
});
const nodeItem = e.item;
// 設置目標節點的 click 狀態 爲 true
graph.setItemState(nodeItem, 'click', true);
});
// 監聽鼠標點擊節點
graph.on('edge:click', e => {
// 先將所有當前有 click 狀態的邊的 click 狀態置爲 false
const clickEdges = graph.findAllByState('edge', 'click');
clickEdges.forEach(ce => {
graph.setItemState(ce, 'click', false);
});
const edgeItem = e.item;
// 設置目標邊的 click 狀態 爲 true
graph.setItemState(edgeItem, 'click', true);
});
};
main();
</script>
</body>
</html>