refactor(floating-edge-example): cleanup
This commit is contained in:
Generated
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"..": {
|
"..": {
|
||||||
"name": "react-flow-renderer",
|
"name": "react-flow-renderer",
|
||||||
"version": "9.6.7",
|
"version": "9.6.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.15.4",
|
"@babel/runtime": "^7.15.4",
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { getBezierPath, ConnectionLineComponentProps } from 'react-flow-renderer';
|
||||||
|
|
||||||
|
import { getArrow } from './utils';
|
||||||
|
|
||||||
|
const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({
|
||||||
|
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 (
|
||||||
|
<g>
|
||||||
|
<path fill="none" stroke="#222" strokeWidth={1.5} className="animated" d={d} />
|
||||||
|
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FloatingConnectionLine;
|
||||||
@@ -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<EdgeProps> = ({ 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 (
|
||||||
|
<g className="react-flow__connection">
|
||||||
|
<path id={id} className="react-flow__edge-path" d={d} markerEnd={markerEnd} style={style as CSSProperties} />
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FloatingEdge;
|
||||||
@@ -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<Elements>(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 (
|
||||||
|
<ReactFlow
|
||||||
|
elements={elements}
|
||||||
|
onElementsRemove={onElementsRemove}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onLoad={onLoad}
|
||||||
|
edgeTypes={edgeTypes}
|
||||||
|
connectionLineComponent={FloatingConnectionLine}
|
||||||
|
>
|
||||||
|
<MiniMap />
|
||||||
|
<Controls />
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodeAsHandleFlow;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<EdgeProps> = ({
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<g className="react-flow__connection">
|
|
||||||
<path
|
|
||||||
id={id}
|
|
||||||
className={cc(['react-flow__edge-path'])}
|
|
||||||
d={d}
|
|
||||||
markerEnd={markerEnd}
|
|
||||||
// opacity={style?.opacity}
|
|
||||||
style={style}
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
{label && (
|
|
||||||
<text>
|
|
||||||
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
|
||||||
{label}
|
|
||||||
</textPath>
|
|
||||||
</text>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConnectionLine: FC<ConnectionLineComponentProps> = ({
|
|
||||||
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 (
|
|
||||||
<g>
|
|
||||||
<path fill="none" stroke="#222" strokeWidth={1.5} className="animated" d={d} />
|
|
||||||
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
|
|
||||||
</g>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CustomEdge;
|
|
||||||
@@ -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<Elements>(initialElements);
|
|
||||||
|
|
||||||
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
|
|
||||||
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge(getCustomEdge(params), els));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ReactFlow
|
|
||||||
elements={elements}
|
|
||||||
onElementsRemove={onElementsRemove}
|
|
||||||
onConnect={onConnect}
|
|
||||||
onLoad={onLoad}
|
|
||||||
edgeTypes={edgeTypes}
|
|
||||||
connectionLineComponent={ConnectionLine}
|
|
||||||
>
|
|
||||||
<MiniMap />
|
|
||||||
<Controls />
|
|
||||||
<Background />
|
|
||||||
</ReactFlow>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NodeAsHandleFlow;
|
|
||||||
@@ -14,7 +14,7 @@ import Provider from './Provider';
|
|||||||
import Hidden from './Hidden';
|
import Hidden from './Hidden';
|
||||||
import EdgeTypes from './EdgeTypes';
|
import EdgeTypes from './EdgeTypes';
|
||||||
import CustomConnectionLine from './CustomConnectionLine';
|
import CustomConnectionLine from './CustomConnectionLine';
|
||||||
import NodeAsHandle from './NodeAsHandle';
|
import FloatingEdges from './FloatingEdges';
|
||||||
import NodeTypeChange from './NodeTypeChange';
|
import NodeTypeChange from './NodeTypeChange';
|
||||||
import NodeTypesObjectChange from './NodeTypesObjectChange';
|
import NodeTypesObjectChange from './NodeTypesObjectChange';
|
||||||
import UpdatableEdge from './UpdatableEdge';
|
import UpdatableEdge from './UpdatableEdge';
|
||||||
@@ -81,8 +81,8 @@ const routes = [
|
|||||||
component: CustomConnectionLine,
|
component: CustomConnectionLine,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/node-as-handle',
|
path: '/floating-edges',
|
||||||
component: NodeAsHandle,
|
component: FloatingEdges,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/nodetype-change',
|
path: '/nodetype-change',
|
||||||
|
|||||||
Generated
+3
@@ -52,6 +52,9 @@
|
|||||||
"start-server-and-test": "^1.14.0",
|
"start-server-and-test": "^1.14.0",
|
||||||
"typescript": "^4.4.3"
|
"typescript": "^4.4.3"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "16 || 17",
|
"react": "16 || 17",
|
||||||
"react-dom": "16 || 17"
|
"react-dom": "16 || 17"
|
||||||
|
|||||||
Reference in New Issue
Block a user