Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions __tests__/graph/graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,44 @@ describe('Graph: Grid / 网格', () => {
cleanup()
})

it('snapToGrid: 支持通过 grid.snapToGrid 关闭自动吸附', () => {
const { graph, cleanup } = createTestGraph({
grid: { size: 10, snapToGrid: false },
})

expect(graph.snapToGrid(12, 18)).toEqual({ x: 12, y: 18 })

cleanup()
})

it('snapToGrid: grid 为 false 时默认关闭自动吸附', () => {
const { graph, cleanup } = createTestGraph({
grid: false,
})

expect(graph.snapToGrid(12, 18)).toEqual({ x: 12, y: 18 })

cleanup()
})

it('drawGrid: 支持动态切换 snapToGrid', () => {
const { graph, cleanup } = createTestGraph({
grid: { size: 10, visible: true },
})

expect(graph.snapToGrid(12, 18)).toEqual({ x: 10, y: 20 })

graph.drawGrid({ snapToGrid: false })
expect(graph.options.grid.snapToGrid).toBe(false)
expect(graph.snapToGrid(12, 18)).toEqual({ x: 12, y: 18 })

graph.drawGrid({ snapToGrid: true })
expect(graph.options.grid.snapToGrid).toBe(true)
expect(graph.snapToGrid(12, 18)).toEqual({ x: 10, y: 20 })

cleanup()
})

it('showGrid / hideGrid / drawGrid: 网格显示控制', () => {
const { graph, cleanup } = createTestGraph()

Expand Down
52 changes: 52 additions & 0 deletions __tests__/plugin/dnd.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,58 @@ describe('Dnd', () => {
expect((result as Node).position()).toEqual({ x: 450, y: 350 })
})

it('should snap dropped node position when grid.snapToGrid is true', () => {
const node = new Rect({
x: 100,
y: 100,
width: 100,
height: 100,
})

graph.options.grid.snapToGrid = true
const result = dnd['drop'](node, {
x: 401,
y: 303,
})

expect((result as Node).position()).toEqual({ x: 450, y: 350 })
})

it('should keep snapline alignment when snapOffset is set', () => {
const node = new Rect({
x: 100,
y: 100,
width: 100,
height: 100,
})

graph.options.grid.snapToGrid = true
dnd['snapOffset'] = { x: 7, y: -8 }
const result = dnd['drop'](node, {
x: 401,
y: 303,
})

expect((result as Node).position()).toEqual({ x: 451, y: 353 })
})

it('should not snap dropped node position when grid.snapToGrid is false', () => {
const node = new Rect({
x: 100,
y: 100,
width: 100,
height: 100,
})

graph.options.grid.snapToGrid = false
const result = dnd['drop'](node, {
x: 401,
y: 303,
})

expect((result as Node).position()).toEqual({ x: 451, y: 353 })
})

it('should return null if is not inside valid area', () => {
const node = new Rect({
width: 100,
Expand Down
3 changes: 3 additions & 0 deletions __tests__/plugin/transform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ function createMockGraph() {
off: vi.fn(),
trigger: vi.fn(),
getPlugin: vi.fn(),
grid: {
isSnapToGridEnabled: () => true,
},
snapToGrid: (x: number, y: number) => ({ x, y }),
getGridSize: () => 1,
findViewByCell: (node: any) => ({
Expand Down
48 changes: 48 additions & 0 deletions __tests__/view/node/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,54 @@ describe('NodeView', () => {
)
expect(node.setPosition).toHaveBeenCalled()
})

it('should snap node position when grid.snapToGrid is true', () => {
const event = { clientX: 153, clientY: 257 } as any
const data = {
moving: true,
offset: { x: -50, y: -50 },
restrict: null,
}

graph.options.grid.snapToGrid = true
vi.spyOn(nodeView, 'getEventData').mockReturnValue(data)
vi.spyOn(nodeView as any, 'autoScrollGraph').mockImplementation(() => {})
vi.spyOn(graph, 'getGridSize').mockReturnValue(10)
vi.spyOn(node, 'setPosition').mockImplementation(() => {})
vi.spyOn(graph.options.embedding, 'enabled', 'get').mockReturnValue(false)

;(nodeView as any).dragNode(event, 153, 257)

expect(node.setPosition).toHaveBeenCalledWith(100, 210, {
restrict: null,
deep: true,
ui: true,
})
})

it('should not snap node position when grid.snapToGrid is false', () => {
const event = { clientX: 153, clientY: 257 } as any
const data = {
moving: true,
offset: { x: -50, y: -50 },
restrict: null,
}

graph.options.grid.snapToGrid = false
vi.spyOn(nodeView, 'getEventData').mockReturnValue(data)
vi.spyOn(nodeView as any, 'autoScrollGraph').mockImplementation(() => {})
vi.spyOn(graph, 'getGridSize').mockReturnValue(10)
vi.spyOn(node, 'setPosition').mockImplementation(() => {})
vi.spyOn(graph.options.embedding, 'enabled', 'get').mockReturnValue(false)

;(nodeView as any).dragNode(event, 153, 257)

expect(node.setPosition).toHaveBeenCalledWith(103, 207, {
restrict: null,
deep: true,
ui: true,
})
})
})

describe('additional methods', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/graph/coord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export class CoordManager extends Base {
typeof x === 'number'
? this.clientToLocalPoint(x, y as number)
: this.clientToLocalPoint(x.x, x.y)
return p.snapToGrid(this.graph.getGridSize())
return this.graph.grid.isSnapToGridEnabled()
? p.snapToGrid(this.graph.getGridSize())
: p
}

localToGraphPoint(x: number | Point | PointLike, y?: number) {
Expand Down
14 changes: 12 additions & 2 deletions src/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ import { CSSManager as Css } from './css'
import { DefsManager as Defs } from './defs'
import type { FilterOptions, GradientOptions, MarkerOptions } from './defs'
import type { EventArgs } from './events'
import { GridManager as Grid, GridDrawOptions } from './grid'
import {
GridManager as Grid,
type GridCommonOptions,
type GridDrawOptions,
} from './grid'
import { HighlightManager as Highlight } from './highlight'
import { MouseWheel as Wheel } from './mousewheel'
import { GraphDefinition, GraphManual, getOptions } from './options'
Expand Down Expand Up @@ -1177,7 +1181,7 @@ export class Graph extends Basecoat<EventArgs> {
return this
}

drawGrid(options?: GridDrawOptions) {
drawGrid(options?: Graph.GridManager.Options) {
this.grid.draw(options)
return this
}
Expand Down Expand Up @@ -1421,3 +1425,9 @@ export class Graph extends Basecoat<EventArgs> {

// #endregion
}

export namespace Graph {
export namespace GridManager {
export type Options = Partial<GridCommonOptions> & GridDrawOptions
}
}
11 changes: 10 additions & 1 deletion src/graph/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export class GridManager extends Base {
return this.grid.size
}

isSnapToGridEnabled() {
return this.grid.snapToGrid
}

setGridSize(size: number) {
this.grid.size = Math.max(size, 1)
this.update()
Expand All @@ -66,7 +70,7 @@ export class GridManager extends Base {
this.elem.style.backgroundImage = ''
}

draw(options?: GridDrawOptions) {
draw(options?: GridManager.Options) {
this.clear()
this.instance = null
Object.assign(this.grid, options)
Expand Down Expand Up @@ -200,6 +204,11 @@ export type GridDrawOptions =
export interface GridCommonOptions {
size: number
visible: boolean
snapToGrid: boolean
}

export type GridOptions = GridCommonOptions & GridDrawOptions

export namespace GridManager {
export type Options = Partial<GridCommonOptions> & GridDrawOptions
}
11 changes: 8 additions & 3 deletions src/graph/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,15 @@ export function getOptions(options: Partial<GraphManual>) {

// grid
// ----
const defaultGrid: GridCommonOptions = { size: 10, visible: false }
const defaultGrid: GridCommonOptions = {
size: 10,
visible: false,
snapToGrid: true,
}
if (typeof grid === 'number') {
result.grid = { size: grid, visible: false }
result.grid = { size: grid, visible: false, snapToGrid: true }
} else if (typeof grid === 'boolean') {
result.grid = { ...defaultGrid, visible: grid }
result.grid = { ...defaultGrid, visible: grid, snapToGrid: grid }
} else {
result.grid = { ...defaultGrid, ...grid }
}
Expand Down Expand Up @@ -400,6 +404,7 @@ export const defaults: Partial<GraphDefinition> = {
grid: {
size: 10,
visible: false,
snapToGrid: true,
},
background: false,

Expand Down
4 changes: 3 additions & 1 deletion src/plugin/dnd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,9 @@ export class Dnd extends View implements GraphPlugin {
const bbox = droppingNode.getBBox()
local.x += bbox.x - bbox.width / 2
local.y += bbox.y - bbox.height / 2
const gridSize = this.snapOffset ? 1 : targetGraph.getGridSize()
const shouldSnapToGrid =
!this.snapOffset && targetGraph.grid.isSnapToGridEnabled()
const gridSize = shouldSnapToGrid ? targetGraph.getGridSize() : 1
Comment thread
ytzry marked this conversation as resolved.

droppingNode.position(
snapToGrid(local.x, gridSize),
Expand Down
6 changes: 4 additions & 2 deletions src/plugin/transform/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,10 @@ export class TransformImpl extends View<TransformImplEventArgs> {
const rawWidth = width
const rawHeight = height

width = snapToGrid(width, gridSize)
height = snapToGrid(height, gridSize)
if (this.graph.grid.isSnapToGridEnabled()) {
width = snapToGrid(width, gridSize)
height = snapToGrid(height, gridSize)
}
width = Math.max(width, options.minWidth || gridSize)
height = Math.max(height, options.minHeight || gridSize)
width = Math.min(width, options.maxWidth || Infinity)
Expand Down
10 changes: 8 additions & 2 deletions src/view/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,8 +1179,14 @@ export class NodeView<

this.autoScrollGraph(e.clientX, e.clientY)

const posX = snapToGrid(x + offset.x, gridSize)
const posY = snapToGrid(y + offset.y, gridSize)
const rawX = x + offset.x
const rawY = y + offset.y
const posX = graph.grid.isSnapToGridEnabled()
? snapToGrid(rawX, gridSize)
: rawX
const posY = graph.grid.isSnapToGridEnabled()
? snapToGrid(rawY, gridSize)
: rawY
node.setPosition(posX, posY, {
restrict,
deep: true,
Expand Down