力导向拖拽元素 DragElementForce
概述
DragElementForce 是 G6 中用于实现 d3-force 和 d3-force-3d 布局下节点拖拽的内置交互。在拖拽过程中会 实时重新计算布局,使得图的布局能够动态调整以适应节点的新位置。
基本用法
在图配置中添加这一交互:
1. 快速配置(静态)
使用字符串形式直接声明,这种方式简洁但仅支持默认配置,且配置后不可动态修改:
const graph = new Graph({
// 其他配置...
behaviors: ['drag-element-force'],
});
2. 对象配置(推荐)
使用对象形式进行配置,支持自定义参数,且可以在运行时动态更新配置:
const graph = new Graph({
// 其他配置...
behaviors: [
{
type: 'drag-element-force',
key: 'drag-element-force-1',
fixed: true, // 拖拽后固定节点位置
},
],
});
配置项
| 配置项 | 说明 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 交互类型名称,这里设置 type: 'drag-element-force' 启用此交互 |
string | drag-element-force |
✓ |
| key | 交互唯一标识符,用于后续操作交互 | string | - | |
| fixed | 在拖拽结束后,节点是否保持固定位置,布尔值代表: - true: 在拖拽结束后,节点的位置将保持固定,不受布局算法的影响 - false: 在拖拽结束后,节点的位置将继续受到布局算法的影响 |
boolean | false | |
| enable | 是否启用拖拽功能,默认可以拖拽节点和 Combo | boolean | ((event: IElementDragEvent) => boolean) | ['node', 'combo'].includes(event.targetType) |
|
| state | 节点选中状态的标识,启用多选时会基于该状态查找选中的节点 | string | selected |
|
| hideEdge | 控制拖拽过程中边的显示状态,可选值有: - none: 不隐藏任何边 - out: 隐藏以当前节点为源节点的边 - in: 隐藏以当前节点为目标节点的边 - both: 隐藏与当前节点相关的所有边 - all: 隐藏图中所有边 ⚠️ 注意:当启用 shadow(幽灵节点)时,hideEdge 配置将不生效。 |
none | all | in | out | both |
none |
|
| cursor | 自定义鼠标样式,示例 | { default?: Cursor; grab: Cursor; grabbing: Cursor } | - | |
| trigger | 同时按下快捷键才能拖拽元素 按键参考: MDN Key Values 。若设为空数组时则表示不需要按下其他按键配合 ⚠️ 注意, trigger 设置为 ['drag'] 时会导致 drag-canvas 行为失效。两者不可同时配置。 |
string[] | (Control | Shift| Alt | ......)[] |
[] |
cursor
cursor 用于自定义拖拽过程中的鼠标指针样式:
default: 默认状态下的指针样式grab: 鼠标悬停在可拖拽元素上时的指针样式grabbing: 正在拖拽时的指针样式
可选值有:auto | default | none | context-menu | help | pointer | progress | wait | cell | crosshair | text | vertical-text | alias | copy | move | no-drop | not-allowed | grab | grabbing | all-scroll | col-resize | row-resize | n-resize | e-resize | s-resize | w-resize | ne-resize | nw-resize | se-resize | sw-resize | ew-resize | ns-resize | nesw-resize | nwse-resize | zoom-in | zoom-out
示例配置:
cursor: {
default: 'default', // 默认使用普通指针
grab: 'grab', // 可拖拽时显示抓取指针
grabbing: 'grabbing' // 拖拽中显示抓取中指针
}
常见问题
1. DragElementForce 和 DragElement 有什么区别?
DragElementForce专门用于d3-force或d3-force-3d布局,拖拽时会实时重新计算布局DragElement是通用的拖拽交互,不会触发布局重新计算
实际案例
网格效果
import { addPanel } from '/demo-runtime.ts';
import { Graph } from '@antv/g6';
function getData(size = 10) {
const nodes = Array.from({ length: size * size }, (_, i) => ({ id: `${i}` }));
const edges = [];
for (let y = 0; y < size; ++y) {
for (let x = 0; x < size; ++x) {
if (y > 0) edges.push({ source: `${(y - 1) * size + x}`, target: `${y * size + x}` });
if (x > 0) edges.push({ source: `${y * size + (x - 1)}`, target: `${y * size + x}` });
}
}
return { nodes, edges };
}
const graph = new Graph({
data: getData(),
layout: {
type: 'd3-force',
manyBody: {
strength: -30,
},
link: {
strength: 1,
distance: 20,
iterations: 10,
},
},
node: {
style: {
size: 10,
fill: '#000',
},
},
edge: {
style: {
stroke: '#000',
},
},
behaviors: [{ type: 'drag-element-force' }, 'zoom-canvas'],
});
graph.render();
addPanel((gui) => {
gui.add({ msg: 'Try to drag nodes' }, 'msg').name('Tips').disable();
});
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}固定被拖拽的节点
import { Graph } from '@antv/g6';
const data = {
nodes: new Array(10).fill(0).map((_, i) => ({ id: `${i}`, label: `${i}` })),
edges: [
{ source: '0', target: '1' },
{ source: '0', target: '2' },
{ source: '0', target: '3' },
{ source: '0', target: '4' },
{ source: '0', target: '5' },
{ source: '0', target: '7' },
{ source: '0', target: '8' },
{ source: '0', target: '9' },
{ source: '2', target: '3' },
{ source: '4', target: '5' },
{ source: '4', target: '6' },
{ source: '5', target: '6' },
],
};
const graph = new Graph({
container: 'container',
data,
node: {
style: {
labelText: (d) => d.label,
labelPlacement: 'middle',
labelFill: '#fff',
},
},
layout: {
type: 'd3-force',
link: {
distance: 100,
strength: 2,
},
collide: {
radius: 40,
},
},
behaviors: [
{
type: 'drag-element-force',
fixed: true,
},
],
});
graph.render();
import { Graph } from '@antv/g6';
import GUI from 'lil-gui';
export function addPanel(renderPanel: (gui: GUI) => void) {
const gui = new GUI({ container: document.body });
gui.title('Control');
Object.assign(gui.domElement.style, { position: 'absolute', top: '0', right: '0', zIndex: '10' });
renderPanel(gui);
addEventListener('pagehide', () => gui.destroy(), { once: true });
}
export async function createGraph(
options: ConstructorParameters<typeof Graph>[0],
size: { width?: number; height?: number } = {},
renderPanel?: (gui: GUI, graph: Graph) => void,
) {
const container = document.createElement('div');
Object.assign(container.style, {
width: '100%',
maxWidth: `${size.width || 600}px`,
height: `${size.height || 400}px`,
});
document.getElementById('container')!.append(container);
const graph = new Graph({ ...size, ...options, width: container.clientWidth, container, autoResize: true });
addEventListener('pagehide', () => graph.destroy(), { once: true });
await graph.render();
if (renderPanel) addPanel((gui) => renderPanel(gui, graph));
return container;
}