套索选择 LassoSelect
概述
鼠标点击拖出一个 不规则的 框框笼罩元素,精准框选范围内的元素会被选中。
使用场景
这一交互主要用于:
- 快速选中一批元素,并且更容易的避开不想选的元素
- 快速取消选中一批元素,并且更容易的避开想保留的元素
在线体验
import { createGraph } from '/demo-runtime.ts';
createGraph(
{
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 100 } },
{ id: 'node-2', style: { x: 360, y: 100 } },
{ id: 'node-3', style: { x: 280, y: 220 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-1', target: 'node-3' },
{ source: 'node-2', target: 'node-3' },
],
},
node: {
style: { fill: '#7e3feb' },
state: {
custom: { fill: '#ffa940' },
},
},
edge: {
stroke: '#8b9baf',
state: {
custom: { stroke: '#ffa940' },
},
},
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
},
],
plugins: [{ type: 'grid-line', size: 30 }],
animation: true,
},
{ width: 600, height: 300 },
(gui, graph) => {
const options = {
key: 'lasso-select',
type: 'lasso-select',
animation: false,
enable: true,
enableElements: ['node', 'edge', 'combo'],
immediately: false,
mode: 'default',
state: 'selected',
trigger: 'shift+drag',
};
const optionFolder = gui.addFolder('LassoSelect Options');
optionFolder.add(options, 'type').disable(true);
optionFolder.add(options, 'animation');
optionFolder.add(options, 'enable');
optionFolder.add(options, 'enableElements', [
['node', 'edge', 'combo'],
['node', 'edge'],
['node', 'combo'],
['combo', 'edge'],
['node'],
['edge'],
['combo'],
]);
optionFolder.add(options, 'trigger', {
'shift+drag': ['shift'],
drag: [],
});
optionFolder.add(options, 'state', ['active', 'selected', 'custom']);
optionFolder.add(options, 'mode', ['union', 'intersect', 'diff', 'default']).onChange((e) => {
immediately.show(e === 'default');
});
const immediately = optionFolder.add(options, 'immediately');
optionFolder.onChange(({ property, value }) => {
graph.updateBehavior({
key: 'lasso-select',
[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;
}基本用法
在图配置中添加这一交互:
1. 快速配置(静态)
使用字符串形式直接声明,这种方式简洁但仅支持默认配置,且配置后不可动态修改:
const graph = new Graph({
// 其他配置...
behaviors: ['lasso-select'],
});
2. 对象配置(推荐)
使用对象形式进行配置,支持自定义参数,且可以在运行时动态更新配置:
const graph = new Graph({
// 其他配置...
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
immediately: true, // 可以看到框框笼罩过去时,元素立即被框选了
trigger: ['shift', 'alt', 'control'], // 配合多种按键进行框选
},
],
});
配置项
| 配置项 | 说明 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 交互类型名称。此插件已内置,你可以通过 type: 'lasso-select' 来使用它。 |
lasso-select | string |
lasso-select |
✓ |
| animation | 是否启用动画 | boolean | false | |
| enable | 是否启用框选功能 | boolean | ((event: Event) => boolean) | true | |
| enableElements | 可框选的元素类型 | ( node | edge | combo )[] |
[node, combo, edge] |
|
| immediately | 是否及时框选, 仅在框选模式 mode为 default 时生效 |
boolean | false | |
| mode | 框选的选择模式 | union | intersect | diff | default |
default |
|
| onSelect | 框选元素状态回调 | (states:Record<string,string|string[]>) =>Record<string,string|string[]> | ||
| state | 被选中时切换到该状态 | string | selected | active | inactive | disabled | highlight |
selected |
|
| style | 框选时的 框样式 | RectStyleProps extends BaseStyleProps | 默认值 | |
| trigger | 按下该快捷键配合鼠标点击进行框选 按键参考: MDN Key Values | string[] | (Control | Shift| Alt | ......)[] |
[shift] |
immediately
是否及时框选, 仅在框选模式为 default 时生效
const graph = new Graph({
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
immediately: true, // 可以看到框框笼罩过去时,元素立即被框选了
trigger: [], // 不需要配合其他按键,点击鼠标拖动即可框选
},
],
});
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
width: 400,
height: 200,
data: {
nodes: [
{ id: 'node-1', style: { x: 100, y: 50 } },
{ id: 'node-2', style: { x: 260, y: 50 } },
{ id: 'node-3', style: { x: 280, y: 100 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-1', target: 'node-3' },
{ source: 'node-2', target: 'node-3' },
],
},
node: {
style: { fill: '#7e3feb' },
},
edge: {
stroke: '#8b9baf',
},
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
immediately: true, // 立即框选
trigger: [],
},
],
plugins: [{ type: 'grid-line', size: 30 }],
});
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;
}mode
框选的选择模式
union:保持已选元素的当前状态,并添加指定的 state 状态。intersect:如果已选元素已有指定的 state 状态,则保留;否则清除该状态。diff:对已选元素的指定 state 状态进行取反操作。default:清除已选元素的当前状态,并添加指定的 state 状态。
const graph = new Graph({
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
mode: 'default', // 框选模式, 默认框选模式
},
],
});
import { createGraph } from '/demo-runtime.ts';
createGraph(
{
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 100 } },
{ id: 'node-2', style: { x: 360, y: 100 } },
{ id: 'node-3', style: { x: 280, y: 220 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-1', target: 'node-3' },
{ source: 'node-2', target: 'node-3' },
],
},
node: {
style: { fill: '#7e3feb' },
state: {
custom: { fill: '#ffa940' },
},
},
edge: {
stroke: '#8b9baf',
state: {
custom: { stroke: '#ffa940' },
},
},
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
trigger: [],
immediately: true,
},
],
plugins: [{ type: 'grid-line', size: 30 }],
animation: true,
},
{ width: 600, height: 300 },
(gui, graph) => {
const options = {
key: 'lasso-select',
type: 'lasso-select',
animation: false,
enable: true,
enableElements: ['node', 'edge', 'combo'],
mode: 'default',
state: 'selected',
};
const optionFolder = gui.addFolder('lassoSelect Options');
optionFolder.add(options, 'type').disable(true);
optionFolder.add(options, 'state', ['active', 'selected', 'custom']);
optionFolder.add(options, 'mode', ['union', 'intersect', 'diff', 'default']);
// .onChange((e) => {
// immediately.show(e === 'default');
// });
optionFolder.onChange(({ property, value }) => {
graph.updateBehavior({
key: 'lasso-select',
[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;
}style
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| cursor | 鼠标样式 | string | |
| fill | 填充颜色 | string | Pattern | null | #1677FF |
| fillOpacity | 填充透明度 | number | string | 0.1 |
| isBillboard | 是否启用公告牌模式 | boolean | |
| isSizeAttenuation | 是否启用大小衰减 | boolean | |
| lineCap | 线段端点样式 | butt | round | square |
|
| lineDash | 虚线配置 | number | string | (string | number)[] | |
| lineDashOffset | 虚线偏移量 | number | |
| lineJoin | 线段连接处样式 | miter | round | bevel |
|
| lineWidth | 线宽度 | number | string | 1 |
| opacity | 整体透明度 | number | string | |
| radius | 矩形圆角半径 | number | string | number[] | |
| shadowBlur | 阴影模糊程度 | number | |
| shadowColor | 阴影颜色 | string | |
| shadowOffsetX | 阴影 X 方向偏移 | number | |
| shadowOffsetY | 阴影 Y 方向偏移 | number | |
| stroke | 描边颜色 | string | Pattern | null | #1677FF |
| strokeOpacity | 描边透明度 | number | string | |
| visibility | 可见性 | visible | hidden |
|
| zIndex | 渲染层级 | number | 2 |
示例:
const graph = new Graph({
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
style: {
width: 0,
height: 0,
lineWidth: 4,
lineDash: [2, 2], // 虚线外框
// rgb超级炫彩框框
fill: 'linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%),linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%),linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%)',
stroke: 'pink',
fillOpacity: 0.2,
zIndex: 2,
pointerEvents: 'none',
},
},
],
});
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
width: 600,
height: 300,
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 100 } },
{ id: 'node-2', style: { x: 360, y: 100 } },
{ id: 'node-3', style: { x: 280, y: 220 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-1', target: 'node-3' },
{ source: 'node-2', target: 'node-3' },
],
},
node: {
style: { fill: '#7e3feb' },
},
edge: {
stroke: '#8b9baf',
},
behaviors: [
{
type: 'lasso-select',
key: 'lasso-select',
trigger: [],
immediately: true,
style: {
width: 0,
height: 0,
lineWidth: 4,
lineDash: [2, 2], // 虚线外框
// rgb超级炫彩框框
fill: 'linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%),linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%),linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%)',
stroke: 'pink',
fillOpacity: 0.2,
zIndex: 2,
pointerEvents: 'none',
},
},
],
plugins: [{ type: 'grid-line', size: 30 }],
animation: 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;
}trigger
按下该快捷键配合鼠标点击进行框选,若设为空数组时则表示鼠标点击进行框选,不需要按下其他按键配合。
注意,trigger 设置为 ['drag'] 时会导致 drag-canvas 行为失效。两者不可同时配置。
实际案例
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 250 } },
{ id: 'node-2', style: { x: 250, y: 200 } },
{ id: 'node-3', style: { x: 300, y: 250 } },
{ id: 'node-4', style: { x: 250, y: 300 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-2', target: 'node-3' },
{ source: 'node-3', target: 'node-4' },
{ source: 'node-4', target: 'node-1' },
],
},
behaviors: [
{
key: 'lasso-select',
type: 'lasso-select',
enable: true,
animation: false,
mode: 'default', // union intersect diff default
state: 'selected', // 'active', 'selected', 'inactive', ...
trigger: [], // ['Shift', 'Alt', 'Control', 'Drag', 'Meta', ...]
style: {
width: 0,
height: 0,
lineWidth: 4,
lineDash: [2, 2],
fill: 'linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%),linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%),linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%)',
stroke: 'pink',
fillOpacity: 0.2,
zIndex: 2,
pointerEvents: 'none',
},
},
],
});
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;
}