From ab2391fd5151c38045eab7a04950ec7115ba78aa Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 14 Oct 2021 11:34:06 +0200 Subject: [PATCH] refactor(floating-edge-example): cleanup --- example/package-lock.json | 2 +- .../FloatingEdges/FloatingConnectionLine.tsx | 36 ++++ example/src/FloatingEdges/FloatingEdge.tsx | 35 ++++ example/src/FloatingEdges/index.tsx | 52 ++++++ example/src/FloatingEdges/utils.ts | 94 ++++++++++ example/src/NodeAsHandle/CustomEdge.tsx | 163 ------------------ example/src/NodeAsHandle/index.tsx | 83 --------- example/src/index.tsx | 6 +- package-lock.json | 3 + 9 files changed, 224 insertions(+), 250 deletions(-) create mode 100644 example/src/FloatingEdges/FloatingConnectionLine.tsx create mode 100644 example/src/FloatingEdges/FloatingEdge.tsx create mode 100644 example/src/FloatingEdges/index.tsx create mode 100644 example/src/FloatingEdges/utils.ts delete mode 100644 example/src/NodeAsHandle/CustomEdge.tsx delete mode 100644 example/src/NodeAsHandle/index.tsx diff --git a/example/package-lock.json b/example/package-lock.json index 3560909ac..520b9afe5 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -27,7 +27,7 @@ }, "..": { "name": "react-flow-renderer", - "version": "9.6.7", + "version": "9.6.8", "license": "MIT", "dependencies": { "@babel/runtime": "^7.15.4", diff --git a/example/src/FloatingEdges/FloatingConnectionLine.tsx b/example/src/FloatingEdges/FloatingConnectionLine.tsx new file mode 100644 index 000000000..f45b16dc6 --- /dev/null +++ b/example/src/FloatingEdges/FloatingConnectionLine.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react'; +import { getBezierPath, ConnectionLineComponentProps } from 'react-flow-renderer'; + +import { getArrow } from './utils'; + +const FloatingConnectionLine: FC = ({ + targetX, + targetY, + sourcePosition, + targetPosition, + sourceNode, +}) => { + if (!sourceNode) { + return null; + } + + const targetNode = { __rf: { width: 1, height: 1, position: { x: targetX, y: targetY } } }; + const { sx, sy } = getArrow(sourceNode, targetNode); + const d = getBezierPath({ + sourceX: sx, + sourceY: sy, + sourcePosition, + targetPosition, + targetX, + targetY, + }); + + return ( + + + + + ); +}; + +export default FloatingConnectionLine; diff --git a/example/src/FloatingEdges/FloatingEdge.tsx b/example/src/FloatingEdges/FloatingEdge.tsx new file mode 100644 index 000000000..e39c318f3 --- /dev/null +++ b/example/src/FloatingEdges/FloatingEdge.tsx @@ -0,0 +1,35 @@ +import { FC, useMemo, CSSProperties } from 'react'; +import { EdgeProps, getMarkerEnd, useStoreState, getBezierPath } from 'react-flow-renderer'; + +import { getArrow } from './utils'; + +const FloatingEdge: FC = ({ id, source, target, arrowHeadType, markerEndId, style }) => { + const nodes = useStoreState((state) => state.nodes); + const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); + + const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]); + const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]); + + if (!sourceNode || !targetNode) { + return null; + } + + const { sx, sy, tx, ty, sourcePos, targetPos } = getArrow(sourceNode, targetNode); + + const d = getBezierPath({ + sourceX: sx, + sourceY: sy, + sourcePosition: sourcePos, + targetPosition: targetPos, + targetX: tx, + targetY: ty, + }); + + return ( + + + + ); +}; + +export default FloatingEdge; diff --git a/example/src/FloatingEdges/index.tsx b/example/src/FloatingEdges/index.tsx new file mode 100644 index 000000000..04a5960b5 --- /dev/null +++ b/example/src/FloatingEdges/index.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; + +import ReactFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + OnLoadParams, + EdgeTypesType, + Elements, + Connection, + Edge, + ArrowHeadType, +} from 'react-flow-renderer'; + +import FloatingEdge from './FloatingEdge'; +import FloatingConnectionLine from './FloatingConnectionLine'; +import { createElements } from './utils'; + +const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); + +const initialElements: Elements = createElements(); + +const edgeTypes: EdgeTypesType = { + floating: FloatingEdge, +}; + +const NodeAsHandleFlow = () => { + const [elements, setElements] = useState(initialElements); + + const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params: Connection | Edge) => + setElements((els) => addEdge({ ...params, type: 'floating', arrowHeadType: ArrowHeadType.Arrow }, els)); + + return ( + + + + + + ); +}; + +export default NodeAsHandleFlow; diff --git a/example/src/FloatingEdges/utils.ts b/example/src/FloatingEdges/utils.ts new file mode 100644 index 000000000..352e7c3f9 --- /dev/null +++ b/example/src/FloatingEdges/utils.ts @@ -0,0 +1,94 @@ +import { Position, ArrowHeadType, Node } from 'react-flow-renderer'; + +function getNodeIntersection(source: Node, target: Node) { + // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a + const { width: sourceWidth, height: sourceHeight, position: sourcePosition } = source.__rf; + const targetPosition = target.__rf.position; + + const w = sourceWidth / 2; + const h = sourceHeight / 2; + + const x2 = sourcePosition.x + w; + const y2 = sourcePosition.y + h; + const x1 = targetPosition.x; + const y1 = targetPosition.y; + + const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h); + const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h); + const a = 1 / (Math.abs(xx1) + Math.abs(yy1)); + const xx3 = a * xx1; + const yy3 = a * yy1; + const x = w * (xx3 + yy3) + x2; + const y = h * (-xx3 + yy3) + y2; + + return [x, y]; +} + +function getEdgePosition(node: Node, x: number, y: number) { + const n = { ...node.__rf.position, ...node.__rf }; + const nx = Math.round(n.x); + const ny = Math.round(n.y); + const px = Math.round(x); + const py = Math.round(y); + + if (px <= nx + 1) { + return Position.Left; + } + if (px >= nx + n.width - 1) { + return Position.Right; + } + if (py <= ny + 1) { + return Position.Top; + } + if (py >= n.y + n.height - 1) { + return Position.Bottom; + } + + return Position.Top; +} + +export function getArrow(source: any, target: any) { + const [sx, sy] = getNodeIntersection(source, target); + const [tx, ty] = getNodeIntersection(target, source); + + const sourcePos = getEdgePosition(source, sx, sy); + const targetPos = getEdgePosition(target, tx, ty); + + return { + sx, + sy, + tx, + ty, + label: null, + sourcePos, + targetPos, + }; +} + +export function createElements() { + const elements = []; + const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; + + elements.push({ id: 'source', data: { label: 'Source', isTarget: false }, position: center }); + + for (let i = 0; i < 8; i++) { + const degrees = i * (360 / 8); + const radians = degrees * (Math.PI / 180); + const x = 250 * Math.cos(radians) + center.x; + const y = 250 * Math.sin(radians) + center.y; + + elements.push({ id: `${i}`, data: { label: 'Target', isTarget: true }, position: { x, y } }); + + if (i % 2 === 0) { + elements.push({ + id: `source-${i}`, + source: 'source', + target: `${i}`, + type: 'floating', + arrowHeadType: ArrowHeadType.Arrow, + }); + } + } + + return elements; +} diff --git a/example/src/NodeAsHandle/CustomEdge.tsx b/example/src/NodeAsHandle/CustomEdge.tsx deleted file mode 100644 index 988b1751a..000000000 --- a/example/src/NodeAsHandle/CustomEdge.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { FC, useMemo } from 'react'; -import cc from 'classcat'; -import { - EdgeProps, - getMarkerEnd, - useStoreState, - Position, - getBezierPath, - ConnectionLineComponentProps, -} from 'react-flow-renderer'; - -function getNodeIntersection(source: any, target: any) { - // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a - const w = source.__rf.width / 2; - const h = source.__rf.height / 2; - - const x2 = source.__rf.position.x + w; - const y2 = source.__rf.position.y + h; - const x1 = target.__rf.position.x; - const y1 = target.__rf.position.y; - - const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h); - const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h); - const a = 1 / (Math.abs(xx1) + Math.abs(yy1)); - const xx3 = a * xx1; - const yy3 = a * yy1; - const x = w * (xx3 + yy3) + x2; - const y = h * (-xx3 + yy3) + y2; - - return [x, y]; -} - -function getEdgePosition(node: any, x: number, y: number) { - const n = { ...node.__rf.position, ...node.__rf }; - const nx = Math.round(n.x); - const ny = Math.round(n.y); - const px = Math.round(x); - const py = Math.round(y); - - if (px <= nx + 1) { - return Position.Left; - } - if (px >= nx + n.width - 1) { - return Position.Right; - } - if (py <= ny + 1) { - return Position.Top; - } - if (py >= n.y + n.height - 1) { - return Position.Bottom; - } - - return Position.Top; -} - -function getArrow(source: any, target: any) { - const [sx, sy] = getNodeIntersection(source, target); - const [tx, ty] = getNodeIntersection(target, source); - - const sourcePos = getEdgePosition(source, sx, sy); - const targetPos = getEdgePosition(target, tx, ty); - - return { - sx, - sy, - tx, - ty, - label: null, - sourcePos, - targetPos, - }; -} - -const CustomEdge: FC = ({ - id, - source, - target, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - data, - arrowHeadType, - markerEndId, - style, -}) => { - const nodes = useStoreState((state) => state.nodes); - const markerEnd = getMarkerEnd(arrowHeadType, markerEndId); - - const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]); - const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]); - - if (!sourceNode || !targetNode) { - return null; - } - - const { sx, sy, tx, ty, label, sourcePos, targetPos } = getArrow(sourceNode, targetNode); - - const d = getBezierPath({ - sourceX: sx, - sourceY: sy, - sourcePosition: sourcePos, - targetPosition: targetPos, - targetX: tx, - targetY: ty, - }); - - return ( - <> - - - - {label && ( - - - {label} - - - )} - - ); -}; - -export const ConnectionLine: FC = ({ - targetX, - targetY, - sourcePosition, - targetPosition, - sourceNode, -}) => { - if (!sourceNode) { - return null; - } - - const targetNode = { __rf: { width: 1, height: 1, position: { x: targetX, y: targetY } } }; - const { sx, sy } = getArrow(sourceNode, targetNode); - const d = getBezierPath({ - sourceX: sx, - sourceY: sy, - sourcePosition, - targetPosition, - targetX, - targetY, - }); - - return ( - - - - - ); -}; - -export default CustomEdge; diff --git a/example/src/NodeAsHandle/index.tsx b/example/src/NodeAsHandle/index.tsx deleted file mode 100644 index 9e3879d38..000000000 --- a/example/src/NodeAsHandle/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useState } from 'react'; - -import ReactFlow, { - removeElements, - addEdge, - MiniMap, - Controls, - Background, - OnLoadParams, - EdgeTypesType, - Elements, - Connection, - Edge, - ArrowHeadType, -} from 'react-flow-renderer'; - -import CustomEdge, { ConnectionLine } from './CustomEdge'; - -const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView(); - -const getCustomEdge = (props: any) => ({ - type: 'custom', - arrowHeadType: ArrowHeadType.Arrow, - ...props, -}); - -function createElements() { - const elements = []; - const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; - - elements.push({ id: 'source', data: { label: 'Source', isTarget: false }, position: center }); - - for (let i = 0; i < 8; i++) { - const degrees = i * (360 / 8); - const radians = degrees * (Math.PI / 180); - const x = 250 * Math.cos(radians) + center.x; - const y = 250 * Math.sin(radians) + center.y; - - elements.push({ id: `${i}`, data: { label: 'Target', isTarget: true }, position: { x, y } }); - - if (i % 2 === 0) { - elements.push( - getCustomEdge({ - id: `source-${i}`, - source: 'source', - target: `${i}`, - }) - ); - } - } - - return elements; -} - -const initialElements: Elements = createElements(); - -const edgeTypes: EdgeTypesType = { - custom: CustomEdge, -}; - -const NodeAsHandleFlow = () => { - const [elements, setElements] = useState(initialElements); - - const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els)); - const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(getCustomEdge(params), els)); - - return ( - - - - - - ); -}; - -export default NodeAsHandleFlow; diff --git a/example/src/index.tsx b/example/src/index.tsx index 0f8a4b777..8371c0af9 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -14,7 +14,7 @@ import Provider from './Provider'; import Hidden from './Hidden'; import EdgeTypes from './EdgeTypes'; import CustomConnectionLine from './CustomConnectionLine'; -import NodeAsHandle from './NodeAsHandle'; +import FloatingEdges from './FloatingEdges'; import NodeTypeChange from './NodeTypeChange'; import NodeTypesObjectChange from './NodeTypesObjectChange'; import UpdatableEdge from './UpdatableEdge'; @@ -81,8 +81,8 @@ const routes = [ component: CustomConnectionLine, }, { - path: '/node-as-handle', - component: NodeAsHandle, + path: '/floating-edges', + component: FloatingEdges, }, { path: '/nodetype-change', diff --git a/package-lock.json b/package-lock.json index ace876fca..66ed96b67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,9 @@ "start-server-and-test": "^1.14.0", "typescript": "^4.4.3" }, + "engines": { + "node": ">=12" + }, "peerDependencies": { "react": "16 || 17", "react-dom": "16 || 17"