diff --git a/src/utils/edge.ts b/src/utils/edge.ts index e6ae1d3d..7b04463d 100644 --- a/src/utils/edge.ts +++ b/src/utils/edge.ts @@ -1,7 +1,7 @@ import { rectToBox } from './graph' -import { EdgePositions, GraphNode, HandleElement, Position, Rect, Transform, XYPosition } from '~/types' +import { EdgePositions, GraphEdge, GraphNode, HandleElement, Position, Rect, Transform, XYPosition } from '~/types' -export function 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 y = (handle?.y ?? 0) + rect.y const width = handle?.width ?? rect.width @@ -31,7 +31,7 @@ export function getHandlePosition(position: Position, rect: Rect, handle?: Handl } } -export function getHandle(bounds?: HandleElement[], handleId?: string): HandleElement | undefined { +export const getHandle = (bounds?: HandleElement[], handleId?: string): HandleElement | undefined => { if (!bounds) return undefined // there is no handleId when there are no multiple handles/ handles with ids @@ -79,17 +79,31 @@ export const getEdgePositions = ( interface IsEdgeVisibleParams { sourcePos: XYPosition targetPos: XYPosition + sourceWidth: number + sourceHeight: number + targetWidth: number + targetHeight: number width: number height: number transform: Transform } -export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }: IsEdgeVisibleParams): boolean { +export function isEdgeVisible({ + sourcePos, + targetPos, + sourceWidth, + sourceHeight, + targetWidth, + targetHeight, + width, + height, + transform, +}: IsEdgeVisibleParams): boolean { const edgeBox = { x: Math.min(sourcePos.x, targetPos.x), y: Math.min(sourcePos.y, targetPos.y), - x2: Math.max(sourcePos.x, targetPos.x), - y2: Math.max(sourcePos.y, targetPos.y), + x2: Math.max(sourcePos.x + sourceWidth, targetPos.x + targetWidth), + y2: Math.max(sourcePos.y + sourceHeight, targetPos.y + targetHeight), } if (edgeBox.x === edgeBox.x2) { @@ -113,3 +127,36 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform } return overlappingArea > 0 } + +export const groupEdgesByZLevel = (edges: GraphEdge[], nodes: GraphNode[]) => { + let maxLevel = -1 + const nodeIds = nodes.map((n) => n.id) + + const levelLookup = edges.reduce>((tree, edge) => { + const z = edge.z + ? edge.z + : Math.max( + nodes[nodeIds.indexOf(edge.source)].computedPosition.z || 0, + nodes[nodeIds.indexOf(edge.target)].computedPosition.z || 0, + ) + if (tree[z]) { + tree[z].push(edge) + } else { + tree[z] = [edge] + } + + maxLevel = z > maxLevel ? z : maxLevel + + return tree + }, {}) + + return Object.entries(levelLookup).map(([key, edges]) => { + const level = +key + + return { + edges, + level, + isMaxLevel: level === maxLevel, + } + }) +} diff --git a/src/utils/graph.ts b/src/utils/graph.ts index d59a5c69..b48e0e15 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -4,6 +4,7 @@ import { CoordinateExtent, Dimensions, Edge, + EdgeMarkerType, Elements, FlowElements, FlowExportObject, @@ -184,6 +185,7 @@ export const parseNode = (node: Node, nodeExtent: CoordinateExtent, defaults?: P ...defaults, } : defaults + return { ...node, ...(defaults as GraphNode), @@ -202,9 +204,11 @@ export const parseEdge = ( type: edge.type ?? 'default', source: edge.source.toString(), target: edge.target.toString(), + z: typeof edge.style?.zIndex === 'string' ? parseInt(edge.style?.zIndex) : edge.style?.zIndex ?? 0, ...defaults, } : defaults + return { ...edge, ...defaults, @@ -266,24 +270,21 @@ export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]: return nodes.filter((node) => { if (!node || node.selectable === false) return false - const { position = { x: 0, y: 0 }, dimensions = { width: 0, height: 0 }, dragging = false } = node - const nBox = rectToBox({ ...position, ...dimensions }) + const { computedPosition = { x: 0, y: 0 }, dimensions = { width: 0, height: 0 }, dragging = false } = node + const nBox = rectToBox({ ...computedPosition, ...dimensions }) const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x)) const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)) const overlappingArea = Math.ceil(xOverlap * yOverlap) + const notInitialized = + typeof dimensions.width === 'undefined' || + typeof dimensions.height === 'undefined' || + dimensions.width === 0 || + dimensions.height === 0 || + dragging - if (dimensions.width === null || dimensions.height === null || dragging) { - // nodes are initialized with width and height = null - return true - } - - if (partially) { - return overlappingArea > 0 - } - + const partiallyVisible = partially && overlappingArea > 0 const area = dimensions.width * dimensions.height - - return overlappingArea >= area + return notInitialized || partiallyVisible || overlappingArea >= area }) } @@ -331,9 +332,9 @@ export const getTransformForBounds = ( return [x, y, clampedZoom] } -export function calculateXYZPosition(node: GraphNode, result: XYZPosition): XYZPosition { +export const getXYZPos = (node: GraphNode, result: XYZPosition): XYZPosition => { if (!node.parentNode) return result - return calculateXYZPosition(node.parentNode, { + return getXYZPos(node.parentNode, { x: result.x + node.parentNode.position.x, y: result.y + node.parentNode.position.y, z: @@ -341,9 +342,23 @@ export function calculateXYZPosition(node: GraphNode, result: XYZPosition): XYZP }) } -export function isParentSelected(node: GraphNode): boolean { - if (!node.parentNode) return false +export const isParentSelected = (node: GraphNode): boolean => { if (!node.parentNode) return false if (node.parentNode.selected) return true return isParentSelected(node.parentNode) } + +export const getMarkerId = (marker: EdgeMarkerType | undefined): string => { + if (typeof marker === 'undefined') { + return '' + } + + if (typeof marker === 'string') { + return marker + } + + return Object.keys(marker) + .sort() + .map((key: string) => `${key}=${(marker as any)[key]}`) + .join('&') +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 4c7950a6..f03d70ef 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,3 @@ export * from './edge' export * from './graph' export * from './node' -export * from './store' diff --git a/src/utils/store.ts b/src/utils/store.ts deleted file mode 100644 index 88193a32..00000000 --- a/src/utils/store.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ConnectionMode, FlowState, PanOnScrollMode, DefaultNodeTypes, DefaultEdgeTypes, ConnectionLineType } from '~/types' -import { DefaultNode, InputNode, OutputNode, BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components' -import { createHooks } from '~/composables' - -export const defaultNodeTypes: DefaultNodeTypes = { - input: InputNode, - default: DefaultNode, - output: OutputNode, -} - -export const defaultEdgeTypes: DefaultEdgeTypes = { - default: BezierEdge, - straight: StraightEdge, - step: StepEdge, - smoothstep: SmoothStepEdge, -} - -export const initialState = (): FlowState => ({ - elements: [], - nodes: [], - edges: [], - nodeTypes: [], - edgeTypes: [], - - isReady: false, - instance: undefined, - - dimensions: { - width: 0, - height: 0, - }, - transform: [0, 0, 1], - selectedElements: undefined, - selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 }, - - d3Zoom: undefined, - d3Selection: undefined, - d3ZoomHandler: undefined, - minZoom: 0.5, - maxZoom: 2, - translateExtent: [ - [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], - [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], - ], - - nodeExtent: [ - [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], - [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], - ], - preventScrolling: true, - zoomOnScroll: true, - zoomOnPinch: true, - zoomOnDoubleClick: true, - panOnScroll: false, - panOnScrollSpeed: 0.5, - panOnScrollMode: PanOnScrollMode.Free, - paneMoveable: true, - edgeUpdaterRadius: 10, - onlyRenderVisibleElements: false, - defaultZoom: 1, - defaultPosition: [0, 0], - - nodesSelectionActive: false, - selectionActive: false, - - userSelectionRect: { - startX: 0, - startY: 0, - x: 0, - y: 0, - width: 0, - height: 0, - draw: false, - }, - - arrowHeadColor: '#b1b1b7', - connectionLineStyle: {}, - connectionLineType: ConnectionLineType.Bezier, - connectionNodeId: undefined, - connectionHandleId: undefined, - connectionHandleType: 'source', - connectionPosition: { x: NaN, y: NaN }, - connectionMode: ConnectionMode.Loose, - - snapGrid: [15, 15], - snapToGrid: false, - - edgesUpdatable: false, - nodesConnectable: true, - nodesDraggable: true, - elementsSelectable: true, - selectNodesOnDrag: true, - multiSelectionActive: false, - selectionKeyCode: 'Shift', - multiSelectionKeyCode: 'Meta', - zoomActivationKeyCode: 'Meta', - deleteKeyCode: 'Backspace', - - hooks: createHooks(), - loading: undefined, - - markerEndId: undefined, - storageKey: undefined, - - vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : '-', -})