Skip to content

Commit 9c04450

Browse files
authored
feat(behavior): scroll-canvas/drag-canvas support range (#6266)
* feat: scroll-canvas supports range * feat: drag-canvas supports range * test: add range tests * fix: fix cr issue
1 parent b7bf987 commit 9c04450

File tree

4 files changed

+132
-8
lines changed

4 files changed

+132
-8
lines changed

packages/g6/__tests__/unit/behaviors/drag-canvas.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,24 @@ describe('behavior drag canvas', () => {
184184

185185
await expect(graph).toMatchSnapshot(__filename, 'drag-on-element');
186186
});
187+
188+
it('range', () => {
189+
graph.updateBehavior({ key: 'drag-canvas', trigger: 'drag', direction: 'both', range: 0.5 });
190+
191+
const emitDragEvent = (dx: number, dy: number, count: number) => {
192+
for (let i = 0; i < count; i++) {
193+
dispatchCanvasEvent(graph, CommonEvent.DRAG_START, { targetType: 'canvas' });
194+
dispatchCanvasEvent(graph, CommonEvent.DRAG, { movement: { x: dx, y: dy }, targetType: 'canvas' });
195+
dispatchCanvasEvent(graph, CommonEvent.DRAG_END);
196+
}
197+
};
198+
199+
const [canvasWidth, canvasHeight] = graph.getCanvas().getSize();
200+
emitDragEvent(10, 0, 60);
201+
expect(graph.getPosition()[0]).toBeCloseTo(canvasWidth / 2);
202+
emitDragEvent(-10, 0, 60);
203+
expect(graph.getPosition()[0]).toBeCloseTo(-canvasWidth / 2);
204+
emitDragEvent(0, -10, 60);
205+
expect(graph.getPosition()[0]).toBeCloseTo(-canvasHeight / 2);
206+
});
187207
});

packages/g6/__tests__/unit/behaviors/scroll-canvas.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,15 @@ describe('behavior scroll canvas', () => {
4646

4747
it('direction', async () => {
4848
setBehavior({ direction: 'x' });
49-
const [x, y] = graph.getPosition();
49+
let [x, y] = graph.getPosition();
5050
emitWheelEvent({ deltaX: -10, deltaY: -10 });
5151
expect(graph.getPosition()).toBeCloseTo([x + 10, y]);
5252

53+
setBehavior({ direction: 'y' });
54+
[x, y] = graph.getPosition();
55+
emitWheelEvent({ deltaX: -10, deltaY: -10 });
56+
expect(graph.getPosition()).toBeCloseTo([x, y + 10]);
57+
5358
setBehavior({ direction: undefined });
5459
});
5560

@@ -98,6 +103,25 @@ describe('behavior scroll canvas', () => {
98103
expect(graph.getPosition()).toBeCloseTo([x + 10, y]);
99104
});
100105

106+
it('range', () => {
107+
graph.setBehaviors((behavior) => [...behavior, { ...shortcutScrollCanvasOptions, range: 0.5 }]);
108+
109+
const emitArrow = (key: 'ArrowRight' | 'ArrowLeft' | 'ArrowUp' | 'ArrowDown', count: number) => {
110+
for (let i = 0; i < count; i++) {
111+
graph.emit(CommonEvent.KEY_DOWN, { key });
112+
graph.emit(CommonEvent.KEY_UP, { key });
113+
}
114+
};
115+
116+
const [canvasWidth, canvasHeight] = graph.getCanvas().getSize();
117+
emitArrow('ArrowRight', 50);
118+
expect(graph.getPosition()[0]).toBeCloseTo(canvasWidth / 2);
119+
emitArrow('ArrowLeft', 50);
120+
expect(graph.getPosition()[0]).toBeCloseTo(-canvasWidth / 2);
121+
emitArrow('ArrowUp', 50);
122+
expect(graph.getPosition()[1]).toBeCloseTo(-canvasHeight / 2);
123+
});
124+
101125
it('destroy', () => {
102126
graph.destroy();
103127
});

packages/g6/src/behaviors/drag-canvas.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { debounce, isObject } from '@antv/util';
33
import { CommonEvent } from '../constants';
44
import type { RuntimeContext } from '../runtime/types';
55
import type { IKeyboardEvent, IPointerEvent, Vector2, ViewportAnimationEffectTiming } from '../types';
6+
import { getExpandedBBox, getPointBBox, isPointInBBox } from '../utils/bbox';
7+
import { parsePadding } from '../utils/padding';
68
import type { ShortcutKey } from '../utils/shortcut';
79
import { Shortcut } from '../utils/shortcut';
8-
import { multiply } from '../utils/vector';
10+
import { multiply, subtract } from '../utils/vector';
911
import type { BaseBehaviorOptions } from './base-behavior';
1012
import { BaseBehavior } from './base-behavior';
1113

@@ -41,6 +43,13 @@ export interface DragCanvasOptions extends BaseBehaviorOptions {
4143
* @defaultValue `'both'`
4244
*/
4345
direction?: 'x' | 'y' | 'both';
46+
/**
47+
* <zh/> 可拖拽的视口范围,默认最多可拖拽一屏。可以分别设置上、右、下、左四个方向的范围,每个方向的范围在 [0, Infinity] 之间
48+
*
49+
* <en/> The draggable viewport range allows you to drag up to one screen by default. You can set the range for each direction (top, right, bottom, left) individually, with each direction's range between [0, Infinity]
50+
* @defaultValue 1
51+
*/
52+
range?: number | number[];
4453
/**
4554
* <zh/> 触发拖拽的方式,默认使用指针按下拖拽
4655
*
@@ -80,6 +89,7 @@ export class DragCanvas extends BaseBehavior<DragCanvasOptions> {
8089
},
8190
sensitivity: 10,
8291
direction: 'both',
92+
range: 1,
8393
};
8494

8595
private shortcut: Shortcut;
@@ -169,16 +179,45 @@ export class DragCanvas extends BaseBehavior<DragCanvasOptions> {
169179
* @internal
170180
*/
171181
protected async translate(offset: Vector2, animation?: ViewportAnimationEffectTiming) {
172-
let [dx, dy] = offset;
182+
offset = this.clampByDirection(offset);
183+
offset = this.clampByRange(offset);
173184

185+
await this.context.graph.translateBy(offset, animation);
186+
}
187+
188+
private clampByDirection([dx, dy]: Vector2): Vector2 {
174189
const { direction } = this.options;
175190
if (direction === 'x') {
176191
dy = 0;
177192
} else if (direction === 'y') {
178193
dx = 0;
179194
}
195+
return [dx, dy];
196+
}
180197

181-
await this.context.graph.translateBy([dx, dy], animation);
198+
private clampByRange([dx, dy]: Vector2): Vector2 {
199+
const { viewport, canvas } = this.context;
200+
201+
const [canvasWidth, canvasHeight] = canvas.getSize();
202+
const [top, right, bottom, left] = parsePadding(this.options.range);
203+
const range = [canvasHeight * top, canvasWidth * right, canvasHeight * bottom, canvasWidth * left];
204+
const draggableArea = getExpandedBBox(getPointBBox(viewport!.getCanvasCenter()), range);
205+
206+
const nextViewportCenter = subtract(viewport!.getViewportCenter(), [dx, dy, 0]);
207+
if (!isPointInBBox(nextViewportCenter, draggableArea)) {
208+
const {
209+
min: [minX, minY],
210+
max: [maxX, maxY],
211+
} = draggableArea;
212+
213+
if ((nextViewportCenter[0] < minX && dx > 0) || (nextViewportCenter[0] > maxX && dx < 0)) {
214+
dx = 0;
215+
}
216+
if ((nextViewportCenter[1] < minY && dy > 0) || (nextViewportCenter[1] > maxY && dy < 0)) {
217+
dy = 0;
218+
}
219+
}
220+
return [dx, dy];
182221
}
183222

184223
private validate(event: IPointerEvent | IKeyboardEvent) {

packages/g6/src/behaviors/scroll-canvas.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { isFunction, isObject } from '@antv/util';
22
import { CommonEvent } from '../constants';
33
import type { RuntimeContext } from '../runtime/types';
44
import type { IKeyboardEvent, Point } from '../types';
5+
import { getExpandedBBox, getPointBBox, isPointInBBox } from '../utils/bbox';
6+
import { parsePadding } from '../utils/padding';
57
import { Shortcut, ShortcutKey } from '../utils/shortcut';
8+
import { multiply, subtract } from '../utils/vector';
69
import type { BaseBehaviorOptions } from './base-behavior';
710
import { BaseBehavior } from './base-behavior';
811

@@ -42,6 +45,13 @@ export interface ScrollCanvasOptions extends BaseBehaviorOptions {
4245
* - `'y'`: only allow vertical scrolling
4346
*/
4447
direction?: 'x' | 'y';
48+
/**
49+
* <zh/> 可滚动的视口范围,默认最多可滚动一屏。可以分别设置上、右、下、左四个方向的范围,每个方向的范围在 [0, Infinity] 之间
50+
*
51+
* <en/> The scrollable viewport range allows you to scroll up to one screen by default. You can set the range for each direction (top, right, bottom, left) individually, with each direction's range between [0, Infinity]
52+
* @defaultValue 1
53+
*/
54+
range?: number | number[];
4555
/**
4656
* <zh/> 滚动灵敏度
4757
*
@@ -74,6 +84,7 @@ export class ScrollCanvas extends BaseBehavior<ScrollCanvasOptions> {
7484
enable: true,
7585
sensitivity: 1,
7686
preventDefault: true,
87+
range: 0.5,
7788
};
7889

7990
private shortcut: Shortcut;
@@ -130,18 +141,48 @@ export class ScrollCanvas extends BaseBehavior<ScrollCanvasOptions> {
130141
await this.scroll([-diffX, -diffY], event);
131142
};
132143

133-
private formatDisplacement([dx, dy]: Point) {
134-
const { direction, sensitivity } = this.options;
144+
private formatDisplacement(d: Point) {
145+
const { sensitivity } = this.options;
135146

136-
dx = dx * sensitivity;
137-
dy = dy * sensitivity;
147+
d = multiply(d, sensitivity);
148+
d = this.clampByDirection(d);
149+
d = this.clampByRange(d);
138150

151+
return d;
152+
}
153+
154+
private clampByDirection([dx, dy]: Point) {
155+
const { direction } = this.options;
139156
if (direction === 'x') {
140157
dy = 0;
141158
} else if (direction === 'y') {
142159
dx = 0;
143160
}
161+
return [dx, dy] as Point;
162+
}
144163

164+
private clampByRange([dx, dy]: Point) {
165+
const { viewport, canvas } = this.context;
166+
167+
const [canvasWidth, canvasHeight] = canvas.getSize();
168+
const [top, right, bottom, left] = parsePadding(this.options.range);
169+
const range = [canvasHeight * top, canvasWidth * right, canvasHeight * bottom, canvasWidth * left];
170+
const scrollableArea = getExpandedBBox(getPointBBox(viewport!.getCanvasCenter()), range);
171+
172+
const nextViewportCenter = subtract(viewport!.getViewportCenter(), [dx, dy, 0]);
173+
if (!isPointInBBox(nextViewportCenter, scrollableArea)) {
174+
const {
175+
min: [minX, minY],
176+
max: [maxX, maxY],
177+
} = scrollableArea;
178+
179+
if ((nextViewportCenter[0] < minX && dx > 0) || (nextViewportCenter[0] > maxX && dx < 0)) {
180+
dx = 0;
181+
}
182+
if ((nextViewportCenter[1] < minY && dy > 0) || (nextViewportCenter[1] > maxY && dy < 0)) {
183+
dy = 0;
184+
}
185+
}
145186
return [dx, dy] as Point;
146187
}
147188

0 commit comments

Comments
 (0)