框选
在本章节中主要介绍框选插件相关的知识,通过阅读,你可以了解到
- 如何启用选择与框选
- 如何配置多选与严格框选
- 如何设置修饰键与触发事件(eventTypes)
使用
你可以通过插件 Selection 启用选择与框选功能,示例:
import { Graph, Selection } from '@antv/x6'
const graph = new Graph({
background: {
color: '#F2F7FA',
},
})
graph.use(
new Selection({
enabled: true,
}),
)
演示
- 点击选中节点。
- 启用多选,按住 Ctrl/Command 后点击节点多选。
- 启用移动,拖动选框移动节点。
- 启用框选,在画布空白位置按下鼠标左键,拖动选框来框选节点。
- 启用严格框选(strict)模式,观察其对框选的影响。
- 选择与框选配合使用的修饰键,如
alt;按住alt键,在画布空白处按下鼠标左键并拖动选框来框选节点。 - 应用自定义过滤器(排除 circle 节点),圆形节点不能被选中。
- 应用自定义附加内容(显示选中节点个数),选择两个及以上的节点,触发显示自定义内容。
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph, Selection } from '@antv/x6'
import { Settings, State } from './settings.tsx'
export default class Example extends React.Component {
private container: HTMLDivElement
private graph: Graph
componentDidMount() {
this.graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
this.graph.use(
new Selection({
enabled: true,
multiple: true,
rubberband: true,
movable: true,
showNodeSelectionBox: true,
}),
)
this.graph.addNode({
x: 320,
y: 100,
width: 100,
height: 40,
label: 'Rect',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
})
const source = this.graph.addNode({
x: 80,
y: 50,
width: 100,
height: 40,
label: 'Hello',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
})
const target = this.graph.addNode({
shape: 'circle',
x: 240,
y: 200,
width: 60,
height: 60,
label: 'World',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
},
},
})
this.graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
})
}
onSettingChanged = (options: State) => {
this.graph.toggleMultipleSelection(options.multiple)
this.graph.toggleSelectionMovable(options.movable)
this.graph.toggleRubberband(options.rubberband)
this.graph.toggleStrictRubberband(options.strict)
this.graph.setSelectionFilter(options.filter)
this.graph.setRubberbandModifiers(options.modifiers as any)
this.graph.setSelectionDisplayContent(
options.content
? (selection) => {
return `${selection.length} node${
selection.length > 1 ? 's' : ''
} selected.`
}
: null,
)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="selection-app">
<div className="app-side">
<Settings onChange={this.onSettingChanged} />
</div>
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.selection-app {
display: flex;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-side {
bottom: 0;
padding: 0 8px;
}
.app-content {
flex: 1;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
.ant-card {
box-shadow: 0 0 10px 1px #e9e9e9;
}
.ant-card-head-title {
text-align: center;
}
.ant-row {
margin: 16px 0;
text-align: left;
}
.x6-widget-selection-box {
border: 2px dashed #239edd;
}
.x6-widget-selection-inner {
border: 1px solid #239edd;
}
}import React from 'react'
import { Checkbox, Switch, Card, Row, Col } from 'antd'
import type { CheckboxChangeEvent } from 'antd/es/checkbox'
export interface Props {
onChange: (res: State) => void
}
export interface State {
multiple: boolean
rubberband: boolean
strict: boolean
movable: boolean
modifiers: string[]
filter?: string[]
content: boolean
}
export class Settings extends React.Component<Props, State> {
state: State = {
multiple: true,
rubberband: true,
movable: true,
strict: false,
modifiers: [],
content: false,
}
notifyChange() {
this.props.onChange({
...this.state,
})
}
onMultipleChanged = (multiple: boolean) => {
this.setState({ multiple }, () => {
this.notifyChange()
})
}
onMovableChanged = (movable: boolean) => {
this.setState({ movable }, () => {
this.notifyChange()
})
}
onRubberbandChanged = (rubberband: boolean) => {
this.setState({ rubberband }, () => {
this.notifyChange()
})
}
onStrictChanged = (strict: boolean) => {
this.setState({ strict }, () => {
this.notifyChange()
})
}
onModifiersChange = (modifiers: string[]) => {
this.setState({ modifiers }, () => {
this.notifyChange()
})
}
onFilterChanged = (e: CheckboxChangeEvent) => {
this.setState(
{
filter: e.target.checked ? ['circle'] : undefined,
},
() => {
this.notifyChange()
},
)
}
onContentChanged = (e: CheckboxChangeEvent) => {
this.setState({ content: e.target.checked }, () => {
this.notifyChange()
})
}
render() {
return (
<Card
title="Selection Settings"
size="small"
bordered={false}
style={{ width: 320 }}
>
<Row align="middle">
<Col span={14}>Enable Multiple Select</Col>
<Col span={10}>
<Switch
checked={this.state.multiple}
onChange={this.onMultipleChanged}
/>
</Col>
</Row>
<Row align="middle">
<Col span={14}>Selection Movable</Col>
<Col span={10}>
<Switch
checked={this.state.movable}
onChange={this.onMovableChanged}
/>
</Col>
</Row>
<Row
align="middle"
style={{
margin: '0 -12px',
}}
>
<Col span={24} style={{ borderBottom: '1px solid #e9e9e9' }} />
</Row>
<Row align="middle">
<Col span={14}>Enable Rubberband</Col>
<Col span={10}>
<Switch
checked={this.state.rubberband}
onChange={this.onRubberbandChanged}
/>
</Col>
</Row>
<Row align="middle">
<Col span={14}>Is Strict Contains</Col>
<Col span={10}>
<Switch
checked={this.state.strict}
onChange={this.onStrictChanged}
/>
</Col>
</Row>
<Row align="middle">
<Col span={8}>Modifier Key</Col>
<Col span={16}>
<Checkbox.Group
options={['alt', 'ctrl', 'shift']}
value={this.state.modifiers}
onChange={this.onModifiersChange}
/>
</Col>
</Row>
<Row
align="middle"
style={{
margin: '0 -12px',
}}
>
<Col span={24} style={{ borderBottom: '1px solid #e9e9e9' }} />
</Row>
<Row align="middle">
<Col span={24}>
<Checkbox
checked={this.state.filter != null}
onChange={this.onFilterChanged}
>
Add filter (only circle)
</Checkbox>
</Col>
</Row>
<Row align="middle">
<Col span={24}>
<Checkbox
checked={this.state.content}
onChange={this.onContentChanged}
>
Add content(display selected count)
</Checkbox>
</Col>
</Row>
</Card>
)
}
}配置
| 属性名 | 类型 | 默认值 | 必选 | 描述 |
|---|---|---|---|---|
| enabled | boolean | true |
是否启用 Selection 插件 | |
| className | string | - | 附加样式名,用于定制样式 | |
| multiple | boolean | true |
是否启用点击多选,启用后按住 ctrl 或 command 键点击节点实现多选 |
|
| multipleSelectionModifiers | ModifierKey | ['ctrl', 'meta'] |
用于设置上面点击多选配套的修饰键 | |
| rubberband | boolean | false |
是否启用框选节点功能 | |
| rubberNode | boolean | true |
框选时是否将节点纳入框选范围计算 | |
| rubberEdge | boolean | false |
框选时是否将边纳入框选范围计算 | |
| modifiers | ModifierKey | - | 用于设置上面框选配套的修饰键 | |
| strict | boolean | false |
选框是否需要完全包围节点时才选中节点 | |
| movable | boolean | true |
拖动选框时框选的节点是否一起移动 | |
| content | string | - | 设置附加显示的内容 | |
| filter | Filter | - | 节点过滤器 | |
| showNodeSelectionBox | boolean | false |
是否显示节点的选择框 | |
| showEdgeSelectionBox | boolean | false |
是否显示边的选择框 | |
| pointerEvents | 'none' | 'auto' |
auto |
如果打开 showNodeSelectionBox 时,会在节点上方盖一层元素,导致节点的事件无法响应,此时可以配置 pointerEvents: none 来解决,默认值是 auto |
|
| eventTypes | SelectionEventType[] | ['leftMouseDown', 'mouseWheelDown'] |
用于设置框选的触发事件类型 | |
| movingRouterFallback | string | - | 批量拖拽框选节点过程中,支持临时将与选中节点相连边的路由降级为指定路由,拖拽停止并空闲后自动恢复为原始路由(推荐在拖拽大量节点且边的原始路由为 manhattan 时,将 movingRouterFallback 设置为 orth,能有效提升拖拽流畅性) |
Filter 的类型定义如下:
type Filter = string[] | { id: string }[] | (this: Graph, cell: Cell) => boolean
string[]: 节点 shape 数组,指定的节点/边 shape 才能被选中({ id: string })[]: 节点 ID 数组,指定的节点/边才能被选中(this: Graph, cell: Cell) => boolean: 返回 true 的节点/边才能被选中
ModifierKey 的类型定义如下:
type ModifierKey = string | ('alt' | 'ctrl' | 'meta' | 'shift' | 'space')[] | null
X6 中修饰键包括 alt、ctrl、meta、shift、space 五个,设置修饰键后点击鼠标并按下修饰键即可触发相应的行为。如果框选和画布拖拽平移的触发条件完全相同时,即相同事件类型(eventTypes)和修饰键(modifiers),框选的优先级更高(禁用默认的画布拖拽平移);如果触发条件不同,则互不影响。因此在同时开始框选和拖拽画布时,修饰键就非常有用,比如框选和拖拽画布的触发时机都是鼠标左键在画布空白位置按下(leftMouseDown),这时可以为框选和拖拽画布设置不一样的修饰键,达到同时开启又不冲突的效果。支持配置单个(如 alt)或多个(如 ['alt', 'ctrl'])修饰键,通过数组形式配置的多个修饰键是或关系,比如刚刚配置的修饰键表示按下 alt 或 ctrl,如果需要更加灵活的配置,可以使用如下这些形式:
alt表示按下alt。[alt, ctrl], 表示按下alt或ctrl。alt|ctrl表示按下alt或ctrl。alt&ctrl表示同时按下alt和ctrl。alt|ctrl&shift表示同时按下alt和shift或者同时按下ctrl和shift。
SelectionEventType 的类型定义如下:
type SelectionEventType = 'leftMouseDown' | 'mouseWheelDown';
触发框选的交互方式。支持2种形式或者他们之间的组合:
leftMouseDown: 按下鼠标左键移动进行拖拽mouseWheelDown: 按下鼠标滚轮进行拖拽
API
graph.select(…)
select(cells: Cell | string | (Cell | string)[]): this
选中指定的节点/边。需要注意的是,该方法不会取消选中当前选中的节点/边,而是将指定的节点/边追加到选区中。如果同时需要取消选中当前选中的节点/边,请使用 resetSelection(…) 方法。
graph.unselect(…)
unselect(cells: Cell | string | (Cell | string)[]): this
取消选中指定的节点/边。
graph.isSelected(…)
isSelected(cell: Cell | string): boolean
返回指定的节点/边是否被选中。
graph.resetSelection(…)
resetSelection(cells?: Cell | string | (Cell | string)[]): this
先清空选区,然后选中提供的节点/边。
graph.getSelectedCells()
getSelectedCells(): Cell[]
获取选中的节点/边。
graph.cleanSelection()
cleanSelection(): this
清空选区。
graph.isSelectionEmpty()
isSelectionEmpty(): boolean
返回选区是否为空。
graph.isSelectionEnabled()
isSelectionEnabled(): boolean
返回是否启用选择能力。
graph.enableSelection()
enableSelection(): this
启用选择能力。
graph.disableSelection()
disableSelection(): this
禁用选择能力。
graph.toggleSelection(…)
toggleSelection(enabled?: boolean): this
切换选择的启用状态。参数如下:
| 名称 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| enabled | boolean | - | 是否启用选择能力,缺省时切换选择的启用状态。 |
graph.isMultipleSelection()
isMultipleSelection(): boolean
返回是否启用了多选。
graph.enableMultipleSelection()
enableMultipleSelection(): this
启用多选。
graph.disableMultipleSelection()
disableMultipleSelection(): this
禁用多选。
graph.toggleMultipleSelection(…)
toggleMultipleSelection(multiple?: boolean): this
切换多选的启用状态。参数如下:
| 名称 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| multiple | boolean | - | 是否启用多选,缺省时切换多选的启用状态。 |
graph.isSelectionMovable()
isSelectionMovable(): boolean
返回选中的节点/边是否可以被移动。
graph.enableSelectionMovable()
enableSelectionMovable(): this
启用选中的节点/边的移动。
graph.disableSelectionMovable()
disableSelectionMovable(): this
禁用选中节点/边的移动。
graph.toggleSelectionMovable(…)
toggleSelectionMovable(enabled?: boolean): this
切换选中节点/边是否可以被移动。参数如下:
| 名称 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| enabled | boolean | - | 是否启用选中的节点/边的移动,缺省时切换启用状态。 |
graph.isRubberbandEnabled()
isRubberbandEnabled(): boolean
返回是否启用了框选。
graph.enableRubberband()
enableRubberband(): this
启用框选。
graph.disableRubberband()
disableRubberband(): this
禁用框选。
graph.toggleRubberband(…)
toggleRubberband(enabled?: boolean): this
切换框选的启用状态。参数如下:
| 名称 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| enabled | boolean | - | 是否启用框选,缺省时切换启用状态。 |
graph.isStrictRubberband()
isStrictRubberband(): boolean
返回是否启用了严格框选。启用严格框选后,只有节点/边被选框完全包围时才会选中节点/边。
graph.enableStrictRubberband()
enableStrictRubberband(): this
启用严格框选。启用严格框选后,只有节点/边被选框完全包围时才会选中节点/边。
graph.disableStrictRubberband()
disableStrictRubberband(): this
禁用严格框选。禁用严格框选后,只需要选框与节点/边的包围盒相交即可选中节点/边。
graph.toggleStrictRubberband(…)
toggleStrictRubberband(enabled?: boolean): this
切换严格框选的启用状态。参数如下:
| 名称 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| enabled | boolean | - | 是否启用严格框选,缺省时切换启用状态。 |
graph.setSelectionFilter(…)
setSelectionFilter(
filter?:
| null
| (string | { id: string })[]
| ((this: Graph, cell: Cell) => boolean)
): this
设置选择的过滤条件,满足过滤条件的节点/边才能被选中。
开启 filter 后,blank 上 rubberband 框选过程中 box:mousemove 和 box:mouseup 事件返回的 nodes、edges 也会应用相同的过滤规则,因此其预览结果会和最终 selection 保持一致。
graph.setRubberbandModifiers(…)
setRubberbandModifiers(modifiers?: string | ModifierKey[] | null): this
设置框选的修饰键,只有同时按下修饰键时才能触发框选。
graph.setSelectionDisplayContent(…)
setSelectionDisplayContent(
content?:
| null
| false
| string
| ((this: Graph, selection: Selection, contentElement: HTMLElement) => string)
): this
设置选中节点/边的附加显示内容。
事件
| 事件名称 | 参数类型 | 描述 |
|---|---|---|
cell:selected |
{ cell: Cell; options: Model.SetOptions } |
节点/边被选中时触发 |
node:selected |
{ node: Node; options: Model.SetOptions } |
节点被选中时触发 |
edge:selected |
{ edge: Edge; options: Model.SetOptions } |
边被选中时触发 |
cell:unselected |
{ cell: Cell; options: Model.SetOptions } |
节点/边被取消选中时触发 |
node:unselected |
{ node: Node; options: Model.SetOptions } |
节点被取消选中时触发 |
edge:unselected |
{ edge: Edge; options: Model.SetOptions } |
边被取消选中时触发 |
box:mousedown |
{ e: Dom.MouseDownEvent; view: CellView | null; cell: Cell | null; x: number; y: number; nodes: Node[]; edges: Edge[] } |
按下 selection box,或在画布空白处开始 rubberband 框选时触发 |
box:mousemove |
{ e: Dom.MouseMoveEvent; view: CellView | null; cell: Cell | null; x: number; y: number; nodes: Node[]; edges: Edge[] } |
拖动 selection box,或 rubberband 框选过程中实时触发 |
box:mouseup |
{ e: Dom.MouseUpEvent; view: CellView | null; cell: Cell | null; x: number; y: number; nodes: Node[]; edges: Edge[] } |
结束拖动 selection box,或结束 rubberband 框选时触发 |
selection:changed |
{added: Cell[]; removed: Cell[]; selected: Cell[]; options: Model.SetOptions} |
选中的节点/边发生改变(增删)时触发 |
box:* 事件中的参数说明:
x、y:当前鼠标所在的图坐标。nodes、edges:当前 box 对应的节点和边集合。view、cell:当前关联的视图和单元,在 blank 上进行 rubberband 框选时可能为null。
nodes 和 edges 在不同交互阶段表示的含义如下:
- 在画布空白处开始或拖动 rubberband 框选时,
nodes和edges表示当前选框实时命中的节点和边;strict和filter配置会影响该结果。 - 在拖动已有 selection box 时,
nodes和edges表示当前 selection 中的节点和边。
graph.on('node:selected', ({ node }) => {
console.log(node)
})
graph.on('box:mousemove', ({ nodes, edges, cell }) => {
console.log(nodes, edges, cell)
})
// 我们也可以在插件实例上监听事件
selection.on('node:selected', ({ node }) => {
console.log(node)
})