fix(edges): Remove orphaned edges
# What's changed? * Remove source/targetNode properties from GraphEdge type, the source/target will be fetched with getters if necessary * Remove edges when they have no existing source or target nodes that can be found in the state * Add nodes to custom edge test
This commit is contained in:
@@ -46,6 +46,16 @@ describe('test store state', () => {
|
|||||||
|
|
||||||
it('gets custom edge types', () => {
|
it('gets custom edge types', () => {
|
||||||
store.setState({
|
store.setState({
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
position: { x: 50, y: 50 },
|
||||||
|
},
|
||||||
|
],
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CSSProperties } from 'vue'
|
import { CSSProperties } from 'vue'
|
||||||
import { useHandle, useVueFlow } from '../../composables'
|
import { useHandle, useVueFlow } from '../../composables'
|
||||||
import { ConnectionMode, EdgeComponent, GraphEdge, Position } from '../../types'
|
import { ConnectionMode, EdgeComponent, GraphEdge, GraphNode, Position } from '../../types'
|
||||||
import { getEdgePositions, getHandle, getMarkerId } from '../../utils'
|
import { getEdgePositions, getHandle, getMarkerId } from '../../utils'
|
||||||
import { Slots } from '../../context'
|
import { Slots } from '../../context'
|
||||||
import EdgeAnchor from './EdgeAnchor.vue'
|
import EdgeAnchor from './EdgeAnchor.vue'
|
||||||
@@ -9,6 +9,8 @@ import EdgeAnchor from './EdgeAnchor.vue'
|
|||||||
interface EdgeWrapper {
|
interface EdgeWrapper {
|
||||||
id: string
|
id: string
|
||||||
edge: GraphEdge
|
edge: GraphEdge
|
||||||
|
sourceNode: GraphNode
|
||||||
|
targetNode: GraphNode
|
||||||
selectable?: boolean
|
selectable?: boolean
|
||||||
updatable?: boolean
|
updatable?: boolean
|
||||||
}
|
}
|
||||||
@@ -59,21 +61,21 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
|
|||||||
// when connection type is loose we can define all handles as sources
|
// when connection type is loose we can define all handles as sources
|
||||||
const targetNodeHandles = computed(() => {
|
const targetNodeHandles = computed(() => {
|
||||||
if (store.connectionMode === ConnectionMode.Strict) {
|
if (store.connectionMode === ConnectionMode.Strict) {
|
||||||
return edge.value.targetNode.handleBounds.target
|
return props.targetNode.handleBounds.target
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetBounds = edge.value.targetNode.handleBounds.target
|
const targetBounds = props.targetNode.handleBounds.target
|
||||||
const sourceBounds = edge.value.targetNode.handleBounds.source
|
const sourceBounds = props.targetNode.handleBounds.source
|
||||||
return targetBounds ?? sourceBounds
|
return targetBounds ?? sourceBounds
|
||||||
})
|
})
|
||||||
|
|
||||||
const sourceNodeHandles = computed(() => {
|
const sourceNodeHandles = computed(() => {
|
||||||
if (store.connectionMode === ConnectionMode.Strict) {
|
if (store.connectionMode === ConnectionMode.Strict) {
|
||||||
return edge.value.sourceNode.handleBounds.source
|
return props.sourceNode.handleBounds.source
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetBounds = edge.value.sourceNode.handleBounds.target
|
const targetBounds = props.sourceNode.handleBounds.target
|
||||||
const sourceBounds = edge.value.sourceNode.handleBounds.source
|
const sourceBounds = props.sourceNode.handleBounds.source
|
||||||
return sourceBounds ?? targetBounds
|
return sourceBounds ?? targetBounds
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -90,19 +92,19 @@ onMounted(() => {
|
|||||||
[
|
[
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
() => edge.value.sourceNode.position,
|
() => props.sourceNode.position,
|
||||||
() => edge.value.targetNode.position,
|
() => props.targetNode.position,
|
||||||
() => edge.value.sourceNode.computedPosition,
|
() => props.sourceNode.computedPosition,
|
||||||
() => edge.value.targetNode.computedPosition,
|
() => props.targetNode.computedPosition,
|
||||||
() => edge.value.sourceNode.dimensions,
|
() => props.sourceNode.dimensions,
|
||||||
() => edge.value.targetNode.dimensions,
|
() => props.targetNode.dimensions,
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
const { sourceX, sourceY, targetY, targetX } = getEdgePositions(
|
const { sourceX, sourceY, targetY, targetX } = getEdgePositions(
|
||||||
edge.value.sourceNode,
|
props.sourceNode,
|
||||||
sourceHandle.value,
|
sourceHandle.value,
|
||||||
sourcePosition.value,
|
sourcePosition.value,
|
||||||
edge.value.targetNode,
|
props.targetNode,
|
||||||
targetHandle.value,
|
targetHandle.value,
|
||||||
targetPosition.value,
|
targetPosition.value,
|
||||||
)
|
)
|
||||||
@@ -185,8 +187,8 @@ export default {
|
|||||||
<component
|
<component
|
||||||
:is="type"
|
:is="type"
|
||||||
:id="edge.id"
|
:id="edge.id"
|
||||||
:source-node="edge.sourceNode"
|
:source-node="props.sourceNode"
|
||||||
:target-node="edge.targetNode"
|
:target-node="props.targetNode"
|
||||||
:source="edge.source"
|
:source="edge.source"
|
||||||
:target="edge.target"
|
:target="edge.target"
|
||||||
:updatable="props.updatable"
|
:updatable="props.updatable"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useDraggableCore } from '@braks/revue-draggable'
|
import { useDraggableCore } from '@braks/revue-draggable'
|
||||||
import { CSSProperties, isVNode } from 'vue'
|
import { CSSProperties } from 'vue'
|
||||||
import { useVueFlow } from '../../composables'
|
import { useVueFlow } from '../../composables'
|
||||||
import { GraphNode, NodeComponent, SnapGrid } from '../../types'
|
import { GraphNode, NodeComponent, SnapGrid } from '../../types'
|
||||||
import { NodeId, Slots } from '../../context'
|
import { NodeId, Slots } from '../../context'
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ const connectionLineVisible = controlledComputed(
|
|||||||
store.connectionHandleType
|
store.connectionHandleType
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const groups = computed(() => groupEdgesByZLevel(store.getEdges, store.getNodes))
|
|
||||||
|
const getNode = computed(() => (node: string) => store.getNode(node)!)
|
||||||
|
|
||||||
|
const memoizedGroups = useMemoize(groupEdgesByZLevel)
|
||||||
|
const groups = computed(() => memoizedGroups(store.getEdges, getNode.value))
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@@ -39,6 +43,8 @@ export default {
|
|||||||
:id="edge.id"
|
:id="edge.id"
|
||||||
:key="edge.id"
|
:key="edge.id"
|
||||||
:edge="edge"
|
:edge="edge"
|
||||||
|
:source-node="getNode(edge.source)"
|
||||||
|
:target-node="getNode(edge.target)"
|
||||||
:selectable="typeof edge.selectable === 'undefined' ? store.elementsSelectable : edge.selectable"
|
:selectable="typeof edge.selectable === 'undefined' ? store.elementsSelectable : edge.selectable"
|
||||||
:updatable="typeof edge.updatable === 'undefined' ? store.edgesUpdatable : edge.updatable"
|
:updatable="typeof edge.updatable === 'undefined' ? store.edgesUpdatable : edge.updatable"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default {
|
|||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
v-for="node of store.getNodes"
|
v-for="node of store.getNodes"
|
||||||
:id="node.id"
|
:id="node.id"
|
||||||
:key="`vue-flow__node-${node.id}`"
|
:key="node.id"
|
||||||
:node="node"
|
:node="node"
|
||||||
:draggable="typeof node.draggable === 'undefined' ? store.nodesDraggable : !!node.draggable"
|
:draggable="typeof node.draggable === 'undefined' ? store.nodesDraggable : !!node.draggable"
|
||||||
:selectable="typeof node.selectable === 'undefined' ? store.elementsSelectable : !!node.selectable"
|
:selectable="typeof node.selectable === 'undefined' ? store.elementsSelectable : !!node.selectable"
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import {
|
|||||||
ComputedGetters,
|
ComputedGetters,
|
||||||
} from '~/types'
|
} from '~/types'
|
||||||
import {
|
import {
|
||||||
applyNodeChanges as applyNodes,
|
applyChanges,
|
||||||
applyEdgeChanges as applyEdges,
|
|
||||||
connectionExists,
|
connectionExists,
|
||||||
createPositionChange,
|
createPositionChange,
|
||||||
createSelectionChange,
|
createSelectionChange,
|
||||||
@@ -241,6 +240,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
||||||
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||||
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||||
|
if (missingSource || missingTarget) return res
|
||||||
|
|
||||||
const storedEdge = getters.getEdge.value(edge.id)
|
const storedEdge = getters.getEdge.value(edge.id)
|
||||||
|
|
||||||
@@ -249,8 +249,6 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
...state.defaultEdgeOptions,
|
...state.defaultEdgeOptions,
|
||||||
...storedEdge,
|
...storedEdge,
|
||||||
}),
|
}),
|
||||||
sourceNode,
|
|
||||||
targetNode,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -284,12 +282,11 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
||||||
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||||
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||||
|
if (missingTarget || missingSource) return
|
||||||
|
|
||||||
state.edges.push({
|
state.edges.push({
|
||||||
...state.defaultEdgeOptions,
|
...state.defaultEdgeOptions,
|
||||||
...edge,
|
...edge,
|
||||||
sourceNode,
|
|
||||||
targetNode,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -298,9 +295,9 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
const updateEdge: Actions['updateEdge'] = (oldEdge, newConnection) =>
|
const updateEdge: Actions['updateEdge'] = (oldEdge, newConnection) =>
|
||||||
updateEdgeAction(oldEdge, newConnection, state.edges, addEdges)
|
updateEdgeAction(oldEdge, newConnection, state.edges, addEdges)
|
||||||
|
|
||||||
const applyNodeChanges: Actions['applyNodeChanges'] = (changes) => applyNodes(changes, state.nodes)
|
const applyNodeChanges: Actions['applyNodeChanges'] = (changes) => applyChanges(changes, state.nodes, addNodes)
|
||||||
|
|
||||||
const applyEdgeChanges: Actions['applyEdgeChanges'] = (changes) => applyEdges(changes, state.edges)
|
const applyEdgeChanges: Actions['applyEdgeChanges'] = (changes) => applyChanges(changes, state.edges, addEdges)
|
||||||
|
|
||||||
const setState: Actions['setState'] = (options) => {
|
const setState: Actions['setState'] = (options) => {
|
||||||
const skip = ['modelValue', 'nodes', 'edges', 'maxZoom', 'minZoom', 'translateExtent']
|
const skip = ['modelValue', 'nodes', 'edges', 'maxZoom', 'minZoom', 'translateExtent']
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import { State, GraphEdge, GraphNode, ComputedGetters } from '~/types'
|
|||||||
import { getNodesInside, isEdgeVisible } from '~/utils'
|
import { getNodesInside, isEdgeVisible } from '~/utils'
|
||||||
|
|
||||||
export default (state: State): ComputedGetters => {
|
export default (state: State): ComputedGetters => {
|
||||||
|
const nodeIds = computed(() => state.nodes.map((n) => n.id))
|
||||||
|
const edgeIds = computed(() => state.edges.map((e) => e.id))
|
||||||
|
const getNode: ComputedGetters['getNode'] = computed(() => (id: string) => state.nodes[nodeIds.value.indexOf(id)])
|
||||||
|
const getEdge: ComputedGetters['getEdge'] = computed(() => (id: string) => state.edges[edgeIds.value.indexOf(id)])
|
||||||
|
|
||||||
const getEdgeTypes = computed(() => {
|
const getEdgeTypes = computed(() => {
|
||||||
const edgeTypes: Record<string, any> = {
|
const edgeTypes: Record<string, any> = {
|
||||||
...defaultEdgeTypes,
|
...defaultEdgeTypes,
|
||||||
@@ -41,31 +46,50 @@ export default (state: State): ComputedGetters => {
|
|||||||
: nodes ?? []
|
: nodes ?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const edgeHidden = (e: GraphEdge) => {
|
||||||
|
const source = getNode.value(e.source)
|
||||||
|
const target = getNode.value(e.target)
|
||||||
|
|
||||||
|
if (!source || !target) {
|
||||||
|
console.warn(`[vue-flow]: Orphaned edge ${e.id} will be removed.`)
|
||||||
|
state.edges.splice(state.edges.indexOf(e), 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
!e.hidden &&
|
||||||
|
target &&
|
||||||
|
!target.hidden &&
|
||||||
|
source &&
|
||||||
|
!source.hidden &&
|
||||||
|
source.dimensions.width &&
|
||||||
|
source.dimensions.height &&
|
||||||
|
target.dimensions.width &&
|
||||||
|
target.dimensions.height
|
||||||
|
)
|
||||||
|
}
|
||||||
const getEdges = computed<GraphEdge[]>(() => {
|
const getEdges = computed<GraphEdge[]>(() => {
|
||||||
if (!state.onlyRenderVisibleElements)
|
if (!state.onlyRenderVisibleElements) return state.edges.filter(edgeHidden)
|
||||||
return state.edges.filter((e) => !e.hidden && e.targetNode && !e.targetNode.hidden && e.sourceNode && !e.sourceNode.hidden)
|
|
||||||
else
|
return state.edges.filter((e) => {
|
||||||
return state.edges.filter(
|
const source = getNode.value(e.source)!
|
||||||
(e) =>
|
const target = getNode.value(e.target)!
|
||||||
!e.hidden &&
|
|
||||||
e.sourceNode.dimensions.width &&
|
return (
|
||||||
e.sourceNode.dimensions.height &&
|
edgeHidden(e) &&
|
||||||
e.targetNode.dimensions.width &&
|
isEdgeVisible({
|
||||||
e.targetNode.dimensions.height &&
|
sourcePos: source.computedPosition || { x: 0, y: 0 },
|
||||||
!e.targetNode.hidden &&
|
targetPos: target.computedPosition || { x: 0, y: 0 },
|
||||||
!e.sourceNode.hidden &&
|
sourceWidth: source.dimensions.width,
|
||||||
isEdgeVisible({
|
sourceHeight: source.dimensions.height,
|
||||||
sourcePos: e.sourceNode.computedPosition || { x: 0, y: 0 },
|
targetWidth: target.dimensions.width,
|
||||||
targetPos: e.targetNode.computedPosition || { x: 0, y: 0 },
|
targetHeight: target.dimensions.height,
|
||||||
sourceWidth: e.sourceNode.dimensions.width,
|
width: state.dimensions.width,
|
||||||
sourceHeight: e.sourceNode.dimensions.height,
|
height: state.dimensions.height,
|
||||||
targetWidth: e.targetNode.dimensions.width,
|
viewport: state.viewport,
|
||||||
targetHeight: e.targetNode.dimensions.height,
|
})
|
||||||
width: state.dimensions.width,
|
|
||||||
height: state.dimensions.height,
|
|
||||||
viewport: state.viewport,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSelectedNodes: ComputedGetters['getSelectedNodes'] = computed(() => state.nodes.filter((n) => n.selected))
|
const getSelectedNodes: ComputedGetters['getSelectedNodes'] = computed(() => state.nodes.filter((n) => n.selected))
|
||||||
@@ -75,11 +99,6 @@ export default (state: State): ComputedGetters => {
|
|||||||
...(getSelectedEdges.value ?? []),
|
...(getSelectedEdges.value ?? []),
|
||||||
])
|
])
|
||||||
|
|
||||||
const nodeIds = computed(() => state.nodes.map((n) => n.id))
|
|
||||||
const edgeIds = computed(() => state.edges.map((e) => e.id))
|
|
||||||
const getNode: ComputedGetters['getNode'] = computed(() => (id: string) => state.nodes[nodeIds.value.indexOf(id)])
|
|
||||||
const getEdge: ComputedGetters['getEdge'] = computed(() => (id: string) => state.edges[edgeIds.value.indexOf(id)])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getNode,
|
getNode,
|
||||||
getEdge,
|
getEdge,
|
||||||
|
|||||||
@@ -94,9 +94,7 @@ export interface EdgePositions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Internal edge type */
|
/** Internal edge type */
|
||||||
export type GraphEdge<Data = ElementData, SourceNodeData = any, TargetNodeData = SourceNodeData> = Edge<Data> & {
|
export type GraphEdge<Data = ElementData> = Edge<Data> & {
|
||||||
sourceNode: GraphNode<SourceNodeData>
|
|
||||||
targetNode: GraphNode<TargetNodeData>
|
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
z?: number
|
z?: number
|
||||||
} & EdgePositions
|
} & EdgePositions
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { clampPosition, isGraphNode } from './graph'
|
import { clampPosition, isGraphEdge, isGraphNode } from './graph'
|
||||||
import {
|
import {
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
EdgeSelectionChange,
|
EdgeSelectionChange,
|
||||||
ElementChange,
|
ElementChange,
|
||||||
FlowElement,
|
|
||||||
FlowElements,
|
FlowElements,
|
||||||
GraphNode,
|
GraphNode,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
@@ -13,6 +12,9 @@ import {
|
|||||||
CoordinateExtent,
|
CoordinateExtent,
|
||||||
XYPosition,
|
XYPosition,
|
||||||
GraphEdge,
|
GraphEdge,
|
||||||
|
Node,
|
||||||
|
FlowElement,
|
||||||
|
Edge,
|
||||||
} from '~/types'
|
} from '~/types'
|
||||||
|
|
||||||
type CreatePositionChangeParams = {
|
type CreatePositionChangeParams = {
|
||||||
@@ -90,20 +92,25 @@ function handleParentExpand(updateItem: GraphNode, curr: GraphNode[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const applyChanges = <
|
export const applyChanges = <
|
||||||
T extends FlowElement = GraphNode,
|
T extends Node | Edge | FlowElement = Node,
|
||||||
C extends ElementChange = T extends GraphNode ? NodeChange : EdgeChange,
|
C extends ElementChange = T extends GraphNode ? NodeChange : EdgeChange,
|
||||||
>(
|
>(
|
||||||
changes: C[],
|
changes: C[],
|
||||||
elements: T[],
|
elements: T[],
|
||||||
|
addElement?: (els: T[]) => void,
|
||||||
): T[] => {
|
): T[] => {
|
||||||
let elementIds = elements.map((el) => el.id)
|
let elementIds = elements.map((el) => el.id)
|
||||||
changes.forEach((change) => {
|
changes.forEach((change) => {
|
||||||
if (change.type === 'add') return elements.push(change.item as any)
|
if (change.type === 'add') {
|
||||||
|
if (addElement) addElement([change.item as any])
|
||||||
|
else elements.push(change.item as any)
|
||||||
|
}
|
||||||
|
|
||||||
const i = elementIds.indexOf((<any>change).id)
|
const i = elementIds.indexOf((<any>change).id)
|
||||||
const el = elements[i]
|
const el = elements[i]
|
||||||
switch (change.type) {
|
switch (change.type) {
|
||||||
case 'select':
|
case 'select':
|
||||||
el.selected = change.selected
|
if (isGraphNode(el) || isGraphEdge(el)) el.selected = change.selected
|
||||||
break
|
break
|
||||||
case 'position':
|
case 'position':
|
||||||
if (isGraphNode(el)) {
|
if (isGraphNode(el)) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { rectToBox } from './graph'
|
import { rectToBox } from './graph'
|
||||||
import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Viewport, XYPosition } from '~/types'
|
import { EdgePositions, Getters, GraphEdge, GraphNode, HandleElement, Position, Rect, Viewport, XYPosition } from '~/types'
|
||||||
|
|
||||||
export const getHandlePosition = (position: Position, rect: Rect, handle?: HandleElement): XYPosition => {
|
export const getHandlePosition = (position: Position, rect: Rect, handle?: HandleElement): XYPosition => {
|
||||||
const x = (handle?.x ?? 0) + rect.x
|
const x = (handle?.x ?? 0) + rect.x
|
||||||
@@ -126,17 +126,16 @@ export function isEdgeVisible({
|
|||||||
return overlappingArea > 0
|
return overlappingArea > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const groupEdgesByZLevel = (edges: GraphEdge[], nodes: GraphNode[]) => {
|
export const groupEdgesByZLevel = (edges: GraphEdge[], getNode: Getters['getNode']) => {
|
||||||
let maxLevel = -1
|
let maxLevel = -1
|
||||||
const nodeIds = nodes.map((n) => n.id)
|
|
||||||
|
|
||||||
const levelLookup = edges.reduce<Record<string, GraphEdge[]>>((tree, edge) => {
|
const levelLookup = edges.reduce<Record<string, GraphEdge[]>>((tree, edge) => {
|
||||||
const z = edge.z
|
const source = getNode(edge.source)
|
||||||
? edge.z
|
const target = getNode(edge.target)
|
||||||
: Math.max(
|
|
||||||
nodes[nodeIds.indexOf(edge.source)]?.computedPosition.z || 0,
|
if (!source || !target) return tree
|
||||||
nodes[nodeIds.indexOf(edge.target)]?.computedPosition.z || 0,
|
|
||||||
)
|
const z = edge.z ? edge.z : Math.max(source.computedPosition.z || 0, target.computedPosition.z || 0)
|
||||||
if (tree[z]) {
|
if (tree[z]) {
|
||||||
tree[z].push(edge)
|
tree[z].push(edge)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user