diff --git a/packages/g6/__tests__/snapshots/behaviors/click-element/custom-neighborSelectedState.svg b/packages/g6/__tests__/snapshots/behaviors/click-element/custom-neighborSelectedState.svg
new file mode 100644
index 00000000000..e056d2d9d17
--- /dev/null
+++ b/packages/g6/__tests__/snapshots/behaviors/click-element/custom-neighborSelectedState.svg
@@ -0,0 +1,703 @@
+
\ No newline at end of file
diff --git a/packages/g6/__tests__/unit/behaviors/click-element.spec.ts b/packages/g6/__tests__/unit/behaviors/click-element.spec.ts
index 2216671f3a9..b3735d10980 100644
--- a/packages/g6/__tests__/unit/behaviors/click-element.spec.ts
+++ b/packages/g6/__tests__/unit/behaviors/click-element.spec.ts
@@ -31,8 +31,32 @@ describe('behavior click element', () => {
graph.emit(`node:${CommonEvent.CLICK}`, { target: { id: '0' }, targetType: 'node' });
});
+ it('selectedState and neighborSelectedState', async () => {
+ graph.setBehaviors([
+ {
+ type: 'click-element',
+ selectedState: 'selected',
+ neighborSelectedState: 'active',
+ unselectedState: 'inactive',
+ degree: 1,
+ },
+ ]);
+
+ graph.emit(`node:${CommonEvent.CLICK}`, { target: { id: '0' }, targetType: 'node' });
+ await expect(graph).toMatchSnapshot(__filename, 'custom-neighborSelectedState');
+ graph.emit(`node:${CommonEvent.CLICK}`, { target: { id: '0' }, targetType: 'node' });
+ });
+
it('1 degree', async () => {
- graph.setBehaviors([{ type: 'click-element', degree: 1, selectedState: 'selected', unselectedState: undefined }]);
+ graph.setBehaviors([
+ {
+ type: 'click-element',
+ degree: 1,
+ selectedState: 'selected',
+ neighborSelectedState: 'selected',
+ unselectedState: undefined,
+ },
+ ]);
graph.emit(`node:${CommonEvent.CLICK}`, { target: { id: '0' }, targetType: 'node' });
await expect(graph).toMatchSnapshot(__filename, 'node-1-degree');
diff --git a/packages/g6/src/behaviors/click-element.ts b/packages/g6/src/behaviors/click-element.ts
index e9e7db1c613..ab1e1d70488 100644
--- a/packages/g6/src/behaviors/click-element.ts
+++ b/packages/g6/src/behaviors/click-element.ts
@@ -53,16 +53,23 @@ export interface ClickElementOptions extends BaseBehaviorOptions {
*/
trigger?: ShortcutKey;
/**
- * 选中时应用的状态
+ * 当元素被选中时应用的状态
*
- * The state to be applied when select.
+ * The state to be applied when an element is selected
* @defaultValue 'selected'
*/
selectedState?: State;
/**
- * 当有元素被选中时,其他未选中的元素应用的状态
+ * 当有元素选中时,其相邻 n 度关系的元素应用的状态。n 的值由属性 degree 控制,例如 degree 为 1 时表示直接相邻的元素
*
- * The state to be applied on other unselected elements when some elements are selected
+ * The state to be applied to the neighboring elements within n degrees when an element is selected. The value of n is controlled by the degree property, for instance, a degree of 1 indicates direct neighbors
+ * @defaultValue 'selected'
+ */
+ neighborSelectedState?: State;
+ /**
+ * 当有元素被选中时,除了选中元素及其受影响的邻居元素外,其他所有元素应用的状态。
+ *
+ * The state to be applied to all unselected elements when some elements are selected, excluding the selected element and its affected neighbors
* @defaultValue undefined
*/
unselectedState?: State;
@@ -80,7 +87,14 @@ export interface ClickElementOptions extends BaseBehaviorOptions {
*
* For edges, `0 `means only the current edge is selected,`1` means the current edge and its directly adjacent nodes are selected, etc.
*/
- degree?: number;
+ degree?: number | ((event: IPointerEvent) => number);
+ /**
+ * 点击元素时的回调
+ *
+ * Callback when the element is clicked
+ * @param event - 点击事件 | click event
+ */
+ onClick?: (event: IPointerEvent) => void;
}
/**
@@ -93,7 +107,9 @@ export interface ClickElementOptions extends BaseBehaviorOptions {
* When the mouse clicks on an element, you can activate the state of the element, such as selecting nodes or edges. When the degree is 1, clicking on a node will highlight the current node and its directly adjacent nodes and edges.
*/
export class ClickElement extends BaseBehavior {
- private selectedElementIds: ID[] = [];
+ private selectedElementIds: Set = new Set();
+
+ private neighborSelectedElementIds: Set = new Set();
private shortcut: Shortcut;
@@ -103,6 +119,7 @@ export class ClickElement extends BaseBehavior {
multiple: false,
trigger: ['shift'],
selectedState: 'selected',
+ neighborSelectedState: 'selected',
unselectedState: undefined,
degree: 0,
};
@@ -126,50 +143,77 @@ export class ClickElement extends BaseBehavior {
if (!this.validate(event)) return;
this.updateElementsState(event, false);
this.updateElementsState(event, true);
+ this.options.onClick?.(event);
};
private onClickCanvas = (event: IPointerEvent) => {
if (!this.validate(event)) return;
this.updateElementsState(event, false);
- this.selectedElementIds = [];
+ this.selectedElementIds.clear();
+ this.neighborSelectedElementIds.clear();
+ this.options.onClick?.(event);
};
private updateElementsState = (event: IPointerEvent, add: boolean) => {
if (!this.options.selectedState && !this.options.unselectedState) return;
const { graph } = this.context;
- const { targetType, target } = event as { targetType: ElementType; target: Element };
+ const { target } = event as { target: Element };
if (add) {
// 如果当前元素已经被选中,则取消选中 | If the current element is already selected, deselect it
- if (this.selectedElementIds.includes(target.id)) {
- this.selectedElementIds = this.selectedElementIds.filter((id) => id !== target.id);
+ if (this.selectedElementIds.has(target.id)) {
+ this.selectedElementIds.delete(target.id);
} else {
- const selectedElementIds = getElementNthDegreeIds(graph, targetType, target.id, this.options.degree);
const isMultiple = this.options.multiple && this.shortcut.match(this.options.trigger);
- if (!isMultiple) {
- this.selectedElementIds = selectedElementIds;
- } else {
- this.selectedElementIds.push(...selectedElementIds);
- }
+ if (!isMultiple) this.selectedElementIds.clear();
+ this.selectedElementIds.add(target.id);
+ this.updateNeighborSelectedElementIds(event);
}
}
- if (!this.selectedElementIds.length) return;
+ if (!this.selectedElementIds.size) return;
const states: Record = {};
if (this.options.selectedState) {
- Object.assign(states, this.getElementsState(this.selectedElementIds, this.options.selectedState, add));
+ Object.assign(
+ states,
+ this.getElementsState(Array.from(this.selectedElementIds), this.options.selectedState, add),
+ );
+ }
+
+ if (this.options.neighborSelectedState && this.neighborSelectedElementIds.size > 0) {
+ Object.assign(
+ states,
+ this.getElementsState(Array.from(this.neighborSelectedElementIds), this.options.neighborSelectedState, add),
+ );
}
if (this.options.unselectedState) {
- const inactiveIds = idsOf(graph.getData(), true).filter((id) => !this.selectedElementIds.includes(id));
+ const inactiveIds = idsOf(graph.getData(), true).filter(
+ (id) => !this.selectedElementIds.has(id) && !this.neighborSelectedElementIds.has(id),
+ );
Object.assign(states, this.getElementsState(inactiveIds, this.options.unselectedState, add));
}
graph.setElementState(states, this.options.animation);
};
+ private updateNeighborSelectedElementIds = (event: IPointerEvent) => {
+ this.neighborSelectedElementIds.clear();
+
+ const degree = isFunction(this.options.degree) ? this.options.degree(event) : this.options.degree;
+ if (degree) {
+ const { targetType } = event as { targetType: ElementType };
+ this.selectedElementIds.forEach((id) => {
+ const neighborIds = getElementNthDegreeIds(this.context.graph, targetType, id, degree);
+ this.neighborSelectedElementIds = new Set(
+ [...this.neighborSelectedElementIds, ...neighborIds].filter((id) => !this.selectedElementIds.has(id)),
+ );
+ });
+ }
+ };
+
private getElementsState = (ids: ID[], state: State, add: boolean) => {
const { graph } = this.context;
const states: Record = {};