164 lines
3.7 KiB
TypeScript
164 lines
3.7 KiB
TypeScript
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;
|