You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Application-Web/front/components/arrows/BendableArrow.tsx

95 lines
2.4 KiB

import { CSSProperties, ReactElement, useCallback, useEffect, useRef } from "react"
import { add, angle, Pos, relativeTo, size } from "./Pos"
export interface BendableArrowProps {
basePos: Pos
startPos: Pos
endPos: Pos
startRadius?: number
endRadius?: number
style?: ArrowStyle
}
export interface ArrowStyle {
width?: number,
head?: () => ReactElement,
tail?: () => ReactElement,
}
const ArrowStyleDefaults = {
width: 4
}
function constraintInCircle(pos: Pos, from: Pos, radius: number): Pos {
const theta = angle(pos, from)
return {
x: pos.x - Math.sin(theta) * radius,
y: pos.y - Math.cos(theta) * radius
}
}
export default function BendableArrow({ basePos, startPos, endPos, style, startRadius = 0, endRadius = 0 }: BendableArrowProps) {
const svgRef = useRef<SVGSVGElement>(null)
const pathRef = useRef<SVGPathElement>(null);
const styleWidth = style?.width ?? ArrowStyleDefaults.width
const update = () => {
const startRelative = relativeTo(startPos, basePos)
const endRelative = relativeTo(endPos, basePos)
const tailPos = constraintInCircle(startRelative, endRelative, startRadius)
const headPos = constraintInCircle(endRelative, startRelative, endRadius)
// the width and height of the arrow svg
const svgBoxBounds = size(startPos, endPos)
const left = Math.min(tailPos.x, headPos.x)
const top = Math.min(tailPos.y, headPos.y)
const svgStyle: CSSProperties = {
width: `${svgBoxBounds.x}px`,
height: `${svgBoxBounds.y}px`,
left: `${left}px`,
top: `${top}px`,
}
const d = `M${tailPos.x - left} ${tailPos.y - top} L${headPos.x - left} ${headPos.y - top}`
pathRef.current!.setAttribute("d", d)
Object.assign(svgRef.current!.style, svgStyle)
}
useEffect(() => {
//update on resize
window.addEventListener('resize', update)
return () => window.removeEventListener('resize', update)
}, [svgRef, basePos, startPos, endPos])
//update on position changes
useEffect(update, [svgRef, basePos, startPos, endPos])
return (
<svg ref={svgRef} style={{
overflow: "visible",
position: "absolute",
}}>
<path
ref={pathRef}
stroke={"#000"}
strokeWidth={styleWidth} />
</svg>
)
}