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
225 changes: 162 additions & 63 deletions examples/feature-examples/src/pages/graph/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { forEach, map } from 'lodash-es'
import LogicFlow, { ElementState, LogicFlowUtil } from '@logicflow/core'
import LogicFlow, {
ElementState,
OverlapMode,
ModelType,
} from '@logicflow/core'
import '@logicflow/core/es/index.css'

import { Button, Card, Divider, Flex } from 'antd'
Expand Down Expand Up @@ -98,6 +102,7 @@ const data = {
type: 'rect',
x: 600,
y: 200,

properties: {
width: 80,
height: 120,
Expand Down Expand Up @@ -363,92 +368,186 @@ export default function BasicNode() {
}
}

const handleChangeColor = () => {
// overlapMode 测试逻辑
const setOverlapMode = (mode: OverlapMode) => {
const lf = lfRef.current
if (lf) {
const { edges } = lf.graphModel
edges.forEach(({ id }) => {
lf.setProperties(id, {
style: {
stroke: 'blue',
},
})
})
}
if (!lf) return
lf.graphModel.overlapMode = mode
const order = lf.graphModel.sortElements.map((m) => m.modelType)
console.log('[overlapMode]', mode, '排序结果:', order)
}
const setOverlapModeDefault = () => setOverlapMode(OverlapMode.DEFAULT)
const setOverlapModeIncrease = () => setOverlapMode(OverlapMode.INCREASE)
const setOverlapModeEdgeTop = () => setOverlapMode(OverlapMode.EDGE_TOP)

const handleRefreshGraph = () => {
const addOverlapNode = () => {
const lf = lfRef.current
if (lf) {
const data = lf.getGraphRawData()
console.log('current graph data', data)
const refreshData = LogicFlowUtil.refreshGraphId(data)
console.log('after refresh graphId', data)
lf.render(refreshData)

// 测试 getAreaElement API
// const lt: LogicFlow.PointTuple = [550, 130];
// const rb: LogicFlow.PointTuple = [650, 270];
// const areaElements = lf.getAreaElement(lt, rb);
// console.log('areaElements', areaElements);
if (!lf) return
lf.addNode({
id: 'overlap-node',
text: 'overlap-node',
type: 'rect',
x: 400,
y: 150,
properties: { width: 60, height: 60 },
})
}
const deleteOverlapNode = () => {
lfRef.current?.deleteNode('overlap-node')
}
const selectFirstEdge = () => {
const lf = lfRef.current
if (!lf) return
const data = lf.getGraphData() as GraphData
const edgeId = data.edges?.[0]?.id
if (edgeId) {
lf.selectElementById(edgeId)
lf.toFront(edgeId)
console.log('选中并置顶首条边:', edgeId)
}
}
const selectOverlapNode = () => {
const lf = lfRef.current
if (!lf) return
const id = 'overlap-node'
lf.selectElementById(id)
lf.toFront(id)
console.log('选中并置顶重叠节点:', id)
}
const clearSelection = () => {
lfRef.current?.clearSelectElements()
}

// 其他演示用处理函数
const handleActiveElements = () => {
const lf = lfRef.current
if (lf) {
const { nodes, edges } = lf.getSelectElements()
nodes.forEach(({ id }) => {
lf.setProperties(id, {
isHovered: true,
})
})
edges.forEach(({ id }) => {
lf.setProperties(id, {
isHovered: true,
})
})
}
if (!lf) return
const { nodes, edges } = lf.getSelectElements()
nodes.forEach(({ id }) => {
lf.setProperties(id, { isHovered: true })
})
edges.forEach(({ id }) => {
lf.setProperties(id, { isHovered: true })
})
}

const handleTurnAnimationOn = () => {
if (lfRef.current) {
const { edges } = lfRef.current.getGraphData() as GraphData
forEach(edges, (edge) => {
lfRef.current?.openEdgeAnimation(edge.id)
})
}
const lf = lfRef.current
if (!lf) return
const { edges } = lf.getGraphData() as GraphData
forEach(edges, (edge) => {
if ((edge as any).id) lf.openEdgeAnimation((edge as any).id)
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary type casting and null check. The edges array from getGraphData() returns EdgeData[] which always has an id property. Remove the type cast and condition: lf.openEdgeAnimation(edge.id)

Copilot uses AI. Check for mistakes.
})
}

const handleTurnAnimationOff = () => {
if (lfRef.current) {
const { edges } = lfRef.current.getGraphData() as GraphData
forEach(edges, (edge) => {
lfRef.current?.closeEdgeAnimation(edge.id)
})
}
const lf = lfRef.current
if (!lf) return
const { edges } = lf.getGraphData() as GraphData
forEach(edges, (edge) => {
if ((edge as any).id) lf.closeEdgeAnimation((edge as any).id)
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary type casting and null check. The edges array from getGraphData() returns EdgeData[] which always has an id property. Remove the type cast and condition: lf.closeEdgeAnimation(edge.id)

Suggested change
if ((edge as any).id) lf.closeEdgeAnimation((edge as any).id)
lf.closeEdgeAnimation(edge.id)

Copilot uses AI. Check for mistakes.
})
}

const handleDragItem = (cfg: OnDragNodeConfig) => {
const lf = lfRef.current
if (!lf) return
lf.dnd?.startDrag(cfg)
}

const handleRefreshGraph = () => {
const lf = lfRef.current
if (!lf) return
const raw = lf.getGraphRawData?.()
console.log('当前原始数据:', raw || lf.getGraphData())
}

const handleDragItem = (node: OnDragNodeConfig) => {
lfRef?.current?.dnd.startDrag(node)
const handleChangeColor = () => {
const lf = lfRef.current
if (!lf) return
const { edges } = lf.getSelectElements()
edges.forEach(({ id }) => {
lf.setProperties(id, { style: { stroke: '#ff4d4f' } })
})
}

const changeNodeBorderColor = () => {
const lf = lfRef.current
if (lf) {
const { nodes } = lf.getSelectElements()
nodes.forEach(({ id, properties }) => {
console.log('properties', properties)
lf.setProperties(id, {
style: {
stroke: 'pink',
},
})
})
}
if (!lf) return
const { nodes } = lf.getSelectElements()
nodes.forEach(({ id }) => {
lf.setProperties(id, { style: { stroke: '#ff4d4f' } })
})
}

return (
<Card title="Graph">
<Flex wrap="wrap" gap="small">
{/* overlapMode 测试控制 */}
<Button
key="overlap-default"
type="primary"
onClick={setOverlapModeDefault}
>
默认堆叠模式
</Button>
<Button
key="overlap-increase"
type="primary"
onClick={setOverlapModeIncrease}
>
递增堆叠模式
</Button>
<Button
key="overlap-edge-top"
type="primary"
onClick={setOverlapModeEdgeTop}
>
边置顶模式
</Button>
<Button
key="print-sort"
type="primary"
onClick={() => {
const lf = lfRef.current
if (!lf) return
const order = lf.graphModel.sortElements.map((m) =>
m.modelType === ModelType.EDGE ? 'edge' : 'node',
)
console.log('当前渲染排序:', order)
}}
>
打印渲染排序
</Button>
<Button key="add-overlap-node" type="primary" onClick={addOverlapNode}>
添加重叠节点
</Button>
<Button
key="delete-overlap-node"
type="primary"
onClick={deleteOverlapNode}
>
删除重叠节点
</Button>
<Button
key="select-first-edge"
type="primary"
onClick={selectFirstEdge}
>
选中并置顶首条边
</Button>
<Button
key="select-overlap-node"
type="primary"
onClick={selectOverlapNode}
>
选中并置顶重叠节点
</Button>
<Button key="clear-selection" type="primary" onClick={clearSelection}>
取消选中
</Button>
</Flex>
<Divider orientation="left" orientationMargin="5" plain></Divider>
<Flex wrap="wrap" gap="small">
<Button key="arrow1" type="primary" onClick={() => setArrow('half')}>
箭头 1
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/constant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export enum EventType {
}

export enum OverlapMode {
EDGE_TOP = -1, // 边固定在顶部
DEFAULT = 0, // 默认
INCREASE = 1, // 递增
}
Expand Down
51 changes: 48 additions & 3 deletions packages/core/src/model/GraphModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,48 @@ export class GraphModel {
* todo: 性能优化
*/
@computed get sortElements() {
// 在 EDGE_TOP 模式下,先渲染节点,再渲染边,保证边始终在顶部。
if (this.overlapMode === OverlapMode.EDGE_TOP) {
const nodesSorted = [...this.nodes].sort((a, b) => a.zIndex - b.zIndex)
const edgesSorted = [...this.edges].sort((a, b) => a.zIndex - b.zIndex)

const visibleNodes: (BaseNodeModel | BaseEdgeModel)[] = []
const visibleEdges: (BaseNodeModel | BaseEdgeModel)[] = []
const visibleLt: PointTuple = [
-DEFAULT_VISIBLE_SPACE,
-DEFAULT_VISIBLE_SPACE,
]
const visibleRb: PointTuple = [
this.width + DEFAULT_VISIBLE_SPACE,
this.height + DEFAULT_VISIBLE_SPACE,
]

for (let i = 0; i < nodesSorted.length; i++) {
const item = nodesSorted[i]
if (
item.visible &&
(!this.partial ||
item.isSelected ||
this.isElementInArea(item, visibleLt, visibleRb, false, false))
) {
visibleNodes.push(item)
}
}
for (let i = 0; i < edgesSorted.length; i++) {
const item = edgesSorted[i]
if (
item.visible &&
(!this.partial ||
item.isSelected ||
this.isElementInArea(item, visibleLt, visibleRb, false, false))
) {
visibleEdges.push(item)
}
}

return [...visibleNodes, ...visibleEdges]
Comment on lines +254 to +291
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EDGE_TOP sorting logic duplicates the visibility filtering logic from the default path (lines 294-322). Consider extracting the visibility filtering into a helper function to avoid code duplication and improve maintainability.

Copilot uses AI. Check for mistakes.
}

const elements = [...this.nodes, ...this.edges].sort(
(a, b) => a.zIndex - b.zIndex,
)
Expand Down Expand Up @@ -772,7 +814,10 @@ export class GraphModel {
element.setZIndex(ELEMENT_MAX_Z_INDEX)
this.topElement = element
}
if (this.overlapMode === OverlapMode.INCREASE) {
if (
this.overlapMode === OverlapMode.INCREASE ||
this.overlapMode === OverlapMode.EDGE_TOP
) {
this.setElementZIndex(id, 'top')
}
}
Expand Down Expand Up @@ -1584,7 +1629,7 @@ export class GraphModel {
}

/**
* 获取图形区域虚拟矩型的尺寸和中心坐标
* 获取图形区域虚拟矩形的尺寸和中心坐标
* @returns
*/
getVirtualRectSize(): GraphModel.VirtualRectProps {
Expand All @@ -1611,7 +1656,7 @@ export class GraphModel {
const virtualRectWidth = maxX - minX || 0
const virtualRectHeight = maxY - minY || 0

// 获取虚拟矩型的中心坐标
// 获取虚拟矩形的中心坐标
const virtualRectCenterPositionX = minX + virtualRectWidth / 2
const virtualRectCenterPositionY = minY + virtualRectHeight / 2

Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/model/edge/BaseEdgeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ export class BaseEdgeModel<P extends PropertiesType = PropertiesType>
this.isShowAdjustPoint = adjustEdgeStartAndEnd
assign(this, pickEdgeConfig(data))
const { overlapMode } = this.graphModel
if (overlapMode === OverlapMode.INCREASE) {
if (
overlapMode === OverlapMode.INCREASE ||
overlapMode === OverlapMode.EDGE_TOP
) {
this.zIndex = data.zIndex || getZIndex()
}
// 设置边的 anchors,也就是边的两个端点
Expand Down Expand Up @@ -397,7 +400,10 @@ export class BaseEdgeModel<P extends PropertiesType = PropertiesType>
startPoint: assign({}, this.startPoint),
endPoint: assign({}, this.endPoint),
}
if (this.graphModel.overlapMode === OverlapMode.INCREASE) {
if (
this.graphModel.overlapMode === OverlapMode.INCREASE ||
this.graphModel.overlapMode === OverlapMode.EDGE_TOP
) {
data.zIndex = this.zIndex
}
const { x, y, value } = this.text
Expand Down
Loading
Loading