refactor(floating-edge-example): cleanup

This commit is contained in:
moklick
2021-10-14 11:34:06 +02:00
parent a50f9c69af
commit ab2391fd51
9 changed files with 224 additions and 250 deletions
@@ -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;
+52
View File
@@ -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;
+94
View File
@@ -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;
}
-163
View File
@@ -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;
-83
View File
@@ -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;
+3 -3
View File
@@ -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',