对齐线 Snapline
概述
对齐线插件为画布提供智能对齐参考线,在移动节点时自动显示辅助线并支持自动吸附,帮助用户实现精确对齐,是图形编辑中提升效率和精确度的重要工具。
使用场景
对齐线插件主要适用于以下场景:
- 手动调整节点位置时,需要与其他节点精确对齐
- 拖拽移动多个节点时,保持它们之间的对齐关系
- 制作规范的图形布局时,确保节点间距和位置的一致性
- 通过自动吸附功能提高节点排版效率
基本用法
以下是一个简单的 Snapline 插件初始化示例:
const graph = new Graph({
plugins: [
{
type: 'snapline',
key: 'my-snapline', // 指定唯一标识符
tolerance: 5, // 对齐吸附阈值
offset: 20, // 对齐线延伸距离
autoSnap: true, // 启用自动吸附
},
],
});
在线体验
import { createGraph } from '/demo-runtime.ts';
createGraph(
{
data: {
nodes: [
{ id: 'node-0' },
{ id: 'node-1' },
{ id: 'node-2' },
{ id: 'node-3' },
{ id: 'node-4' },
{ id: 'node-5' },
],
edges: [
{ source: 'node-0', target: 'node-1' },
{ source: 'node-0', target: 'node-2' },
{ source: 'node-0', target: 'node-3' },
{ source: 'node-0', target: 'node-4' },
{ source: 'node-1', target: 'node-0' },
{ source: 'node-2', target: 'node-0' },
{ source: 'node-3', target: 'node-0' },
{ source: 'node-4', target: 'node-0' },
{ source: 'node-5', target: 'node-0' },
],
},
layout: { type: 'grid' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: [
{ type: 'grid-line', key: 'grid-line', size: 30 },
{
type: 'snapline',
key: 'snapline',
tolerance: 5,
offset: 20,
verticalLineStyle: { stroke: '#F08F56', lineWidth: 2 },
horizontalLineStyle: { stroke: '#17C76F', lineWidth: 2 },
},
],
},
{ width: 600, height: 300 },
(gui, graph) => {
const options = {
type: 'snapline',
tolerance: 5,
offset: 20,
autoSnap: true,
};
const optionFolder = gui.addFolder('Snapline Options');
optionFolder.add(options, 'type').disable(true);
optionFolder.add(options, 'tolerance', 1, 20, 1);
optionFolder.add(options, 'offset', 1, 50, 1);
optionFolder.add(options, 'autoSnap');
optionFolder.onChange(({ property, value }) => {
graph.updatePlugin({
key: 'snapline',
[property]: value,
});
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;
}配置项
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 插件类型 | string | 'snapline' |
✓ |
| key | 插件唯一标识符 | string | - | |
| tolerance | 对齐精度,即触发对齐的距离阈值 | number | 5 | |
| offset | 对齐线头尾的延伸距离 | number | 20 | |
| autoSnap | 是否启用自动吸附 | boolean | true | |
| shape | 指定元素的参照图形: - 'key': 使用元素的主图形作为参照图形- Function: 传入函数时接收元素对象,返回一个图形 |
string | ((node: Node) => DisplayObject) | 'key' |
|
| verticalLineStyle | 垂直对齐线样式 | 支持BaseStyleProps 下的部分配置,详见「对齐线样式配置项」 | { stroke: '#1783FF' } |
|
| horizontalLineStyle | 水平对齐线样式 | 支持BaseStyleProps 下的部分配置,详见「对齐线样式配置项」 | { stroke: '#1783FF' } |
|
| filter | 过滤不需要参与对齐的节点 | (node: Node) => boolean | () => true |
shape
shape 属性用于指定元素的参照图形,支持以下配置方式:
// 使用主图形作为参照
{
type: 'snapline',
shape: 'key'
}
// 使用自定义函数返回参照图形
{
type: 'snapline',
shape: (node) => {
return node.getShape('custom-shape');
}
}
对齐线样式配置
| 配置项 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| stroke | 线条颜色 | string | Pattern | null | '#1783FF' |
| opacity | 整体透明度 | number | string | 1 |
| strokeOpacity | 描边透明度 | number | string | 1 |
| lineWidth | 线宽度 | number | string | 1 |
| lineCap | 线段端点样式 | 'butt' | 'round' | 'square' |
'butt' |
| lineJoin | 线段连接处样式 | 'miter' | 'round' | 'bevel' |
'miter' |
| lineDash | 虚线配置 | number | string | (string | number)[] | - |
| lineDashOffset | 虚线偏移量 | number | 0 |
| shadowBlur | 阴影模糊程度 | number | 0 |
| shadowColor | 阴影颜色 | string | - |
| shadowOffsetX | 阴影 X 方向偏移 | number | 0 |
| shadowOffsetY | 阴影 Y 方向偏移 | number | 0 |
| cursor | 鼠标样式 | string | 'default' |
| zIndex | 渲染层级 | number | 0 |
示例配置
{
type: 'snapline',
horizontalLineStyle: {
stroke: '#F08F56',
strokeOpacity: 0.8,
lineWidth: 2,
lineDash: [4, 4],
lineDashOffset: 0,
opacity: 1,
cursor: 'move',
},
verticalLineStyle: {
stroke: '#17C76F',
strokeOpacity: 0.8,
lineWidth: 2,
lineDash: [4, 4],
lineDashOffset: 0,
opacity: 1,
cursor: 'move',
},
}
代码示例
基础对齐线
最简单的使用方式:
const graph = new Graph({
plugins: ['snapline'],
});
自定义配置
可以根据需要自定义对齐线的行为:
const graph = new Graph({
plugins: [
{
type: 'snapline',
tolerance: 8, // 更大的吸附范围
offset: 30, // 更长的延伸线
horizontalLineStyle: {
stroke: '#1890ff',
lineWidth: 2,
},
filter: (node) => node.id !== 'node-0', // 根据id过滤节点,不参与对齐
},
],
});
实际案例
import { Graph } from '@antv/g6';
const data = {
nodes: [{ id: 'node-0' }, { id: 'node-1' }, { id: 'node-2' }, { id: 'node-3' }, { id: 'node-4' }, { id: 'node-5' }],
edges: [
{ source: 'node-0', target: 'node-1' },
{ source: 'node-0', target: 'node-2' },
{ source: 'node-0', target: 'node-3' },
{ source: 'node-0', target: 'node-4' },
{ source: 'node-1', target: 'node-0' },
{ source: 'node-2', target: 'node-0' },
{ source: 'node-3', target: 'node-0' },
{ source: 'node-4', target: 'node-0' },
{ source: 'node-5', target: 'node-0' },
],
};
const graph = new Graph({
container: 'container',
data,
layout: { type: 'grid' },
behaviors: ['drag-canvas', 'drag-element'],
plugins: [
{
type: 'snapline',
key: 'snapline',
verticalLineStyle: { stroke: '#F08F56', lineWidth: 2 },
horizontalLineStyle: { stroke: '#17C76F', lineWidth: 2 },
autoSnap: false,
},
],
});
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;
}