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
173 changes: 173 additions & 0 deletions __tests__/registry/connector/jumpover.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,177 @@ describe('jumpover connector', () => {
expect(typeof result).toBe('string')
})
})

describe('jumpDirection option', () => {
// Helper: set up two crossing edges.
// otherCell is placed BEFORE mockCell in the edges array so it passes
// the idx <= thisIndex filter and participates in intersection detection.
function setupCrossingEdges(
thisSource: Point,
thisTarget: Point,
otherSource: Point,
otherTarget: Point,
) {
const otherCell = { getConnector: vi.fn(() => ({ name: 'jumpover' })) }
const otherView = {
sourcePoint: otherSource,
targetPoint: otherTarget,
routePoints: [],
}

// otherCell at index 0, mockCell at index 1 => thisIndex=1, otherCell is always included
mockGraph.model.getEdges.mockReturnValue([otherCell, mockCell])
mockGraph.findViewByCell.mockImplementation((cell: any) =>
cell === mockCell ? mockView : otherView,
)

mockView.sourcePoint = thisSource
mockView.targetPoint = thisTarget
mockView.routePoints = []

return { otherCell, otherView }
}

describe('jumpDirection: "horizontal"', () => {
it('should create jump arc on a horizontal line crossing a vertical line', () => {
setupCrossingEdges(
new Point(0, 50),
new Point(100, 50), // this edge: horizontal
new Point(50, 0),
new Point(50, 100), // other edge: vertical
)

const result = jumpover.call(
mockView,
new Point(0, 50),
new Point(100, 50),
[],
{ jumpDirection: 'horizontal' },
) as string

// arc jump produces 'C' (cubic bezier) segments in the serialized path
expect(result).toContain('C')
})

it('should NOT create jump arc on a vertical line crossing a horizontal line', () => {
setupCrossingEdges(
new Point(50, 0),
new Point(50, 100), // this edge: vertical
new Point(0, 50),
new Point(100, 50), // other edge: horizontal
)

const result = jumpover.call(
mockView,
new Point(50, 0),
new Point(50, 100),
[],
{ jumpDirection: 'horizontal' },
) as string

// vertical line should pass through without a jump arc
expect(result).not.toContain('C')
})
})

describe('jumpDirection: "vertical"', () => {
it('should create jump arc on a vertical line crossing a horizontal line', () => {
setupCrossingEdges(
new Point(50, 0),
new Point(50, 100), // this edge: vertical
new Point(0, 50),
new Point(100, 50), // other edge: horizontal
)

const result = jumpover.call(
mockView,
new Point(50, 0),
new Point(50, 100),
[],
{ jumpDirection: 'vertical' },
) as string

expect(result).toContain('C')
})

it('should NOT create jump arc on a horizontal line crossing a vertical line', () => {
setupCrossingEdges(
new Point(0, 50),
new Point(100, 50), // this edge: horizontal
new Point(50, 0),
new Point(50, 100), // other edge: vertical
)

const result = jumpover.call(
mockView,
new Point(0, 50),
new Point(100, 50),
[],
{ jumpDirection: 'vertical' },
) as string

// horizontal line should pass through without a jump arc
expect(result).not.toContain('C')
})
})

describe('jumpDirection: "both" (default)', () => {
it('should create jump arc on a horizontal line when jumpDirection is "both"', () => {
setupCrossingEdges(
new Point(0, 50),
new Point(100, 50), // this edge: horizontal
new Point(50, 0),
new Point(50, 100), // other edge: vertical
)

const result = jumpover.call(
mockView,
new Point(0, 50),
new Point(100, 50),
[],
{ jumpDirection: 'both' },
) as string

expect(result).toContain('C')
})

it('should create jump arc on a vertical line when jumpDirection is "both"', () => {
setupCrossingEdges(
new Point(50, 0),
new Point(50, 100), // this edge: vertical
new Point(0, 50),
new Point(100, 50), // other edge: horizontal
)

const result = jumpover.call(
mockView,
new Point(50, 0),
new Point(50, 100),
[],
{ jumpDirection: 'both' },
) as string

expect(result).toContain('C')
})

it('should default to "both" behavior when jumpDirection is omitted', () => {
setupCrossingEdges(
new Point(0, 50),
new Point(100, 50), // this edge: horizontal
new Point(50, 0),
new Point(50, 100), // other edge: vertical
)

const result = jumpover.call(
mockView,
new Point(0, 50),
new Point(100, 50),
[],
{}, // no jumpDirection
) as string

expect(result).toContain('C')
})
})
})
})
5 changes: 5 additions & 0 deletions examples/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CaseElkExample } from './pages/case/elk'
import { CaseErExample } from './pages/case/er'
import { CaseMindExample } from './pages/case/mind'
import { CaseSwimlaneExample } from './pages/case/swimlane'
import { JumpoverDirectionExample } from './pages/connector/jumpover-direction'
import { OffsetRoundedExample } from './pages/connector/offset-rounded'
import { XmindCurveExample } from './pages/connector/xmind-curve'
import { EdgeExample } from './pages/edge'
Expand Down Expand Up @@ -93,6 +94,10 @@ function App() {
element={<OffsetRoundedExample />}
/>
<Route path="/connector/xmind-curve" element={<XmindCurveExample />} />
<Route
path="/connector/jumpover-direction"
element={<JumpoverDirectionExample />}
/>
<Route path="/tools/clean" element={<ToolsCleanExample />} />
<Route path="/case/bpmn" element={<CaseBpmnExample />} />
<Route path="/case/class" element={<CaseClassExample />} />
Expand Down
132 changes: 132 additions & 0 deletions examples/src/pages/connector/jumpover-direction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useEffect, useRef, useState } from 'react'
import { Graph } from '@antv/x6'
import '../index.less'

type JumpDirection = 'both' | 'horizontal' | 'vertical'

export const JumpoverDirectionExample: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null)
const graphRef = useRef<Graph | null>(null)
const [direction, setDirection] = useState<JumpDirection>('both')

useEffect(() => {
if (!containerRef.current) return

const graph = new Graph({
container: containerRef.current,
grid: true,
width: 800,
height: 500,
})
graphRef.current = graph

// 四个节点,使水平线和垂直线互相交叉
const a = graph.addNode({
x: 50,
y: 220,
width: 80,
height: 40,
label: 'A',
})
const b = graph.addNode({
x: 680,
y: 220,
width: 80,
height: 40,
label: 'B',
})
const c = graph.addNode({
x: 380,
y: 50,
width: 80,
height: 40,
label: 'C',
})
const d = graph.addNode({
x: 380,
y: 410,
width: 80,
height: 40,
label: 'D',
})

// 水平线:A → B(穿越垂直线)
graph.addEdge({
source: a,
target: b,
connector: { name: 'jumpover', args: { jumpDirection: direction } },
attrs: { line: { stroke: '#1890ff', strokeWidth: 2 } },
labels: [{ attrs: { label: { text: '水平线 A→B', fill: '#1890ff' } } }],
})

// 垂直线:C → D(穿越水平线)
graph.addEdge({
source: c,
target: d,
connector: { name: 'jumpover', args: { jumpDirection: direction } },
attrs: { line: { stroke: '#f5222d', strokeWidth: 2 } },
labels: [{ attrs: { label: { text: '垂直线 C→D', fill: '#f5222d' } } }],
})

return () => {
graph.dispose()
graphRef.current = null
}
// 每次 direction 变化时重建画布
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [direction])

return (
<div className="x6-graph-wrap">
<h1>jumpover — jumpDirection 演示</h1>
<div
className="x6-graph-tools"
style={{ display: 'flex', gap: 12, alignItems: 'center' }}
>
<span style={{ fontWeight: 600 }}>jumpDirection:</span>
{(['both', 'horizontal', 'vertical'] as JumpDirection[]).map((v) => (
<label
key={v}
style={{
cursor: 'pointer',
display: 'flex',
gap: 4,
alignItems: 'center',
}}
>
<input
type="radio"
name="jumpDirection"
value={v}
checked={direction === v}
onChange={() => setDirection(v)}
/>
{v}
</label>
))}
</div>
<div
style={{
marginBottom: 12,
paddingLeft: 16,
color: '#666',
fontSize: 13,
}}
>
{direction === 'both' &&
'两条线交叉时,先绘制的那条(水平/垂直均可)会产生跳弧。'}
Comment thread
ylcr003 marked this conversation as resolved.
{direction === 'horizontal' &&
'只有水平线(A→B,蓝色)在交叉处产生跳弧;垂直线(C→D,红色)直线穿过。'}
{direction === 'vertical' &&
'只有垂直线(C→D,红色)在交叉处产生跳弧;水平线(A→B,蓝色)直线穿过。'}
</div>
<div
ref={containerRef}
className="x6-graph"
style={{ width: 800, height: 500 }}
/>
</div>
)
}

export default JumpoverDirectionExample
4 changes: 4 additions & 0 deletions examples/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ const dataSource = [
example: 'connector/xmind-curve',
description: '脑图连接器',
},
{
example: 'connector/jumpover-direction',
description: 'jumpover jumpDirection 演示',
},
// ========= tools =========
{
example: 'tools/clean',
Expand Down
Loading