diff --git a/lib/types.ts b/lib/types.ts index 1ad00af..e04ec90 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -70,6 +70,8 @@ export interface Arrow { color?: string label?: string inlineLabel?: string + layer?: string + step?: number } export type NinePointAnchor = diff --git a/site/components/InteractiveGraphics/InteractiveGraphics.tsx b/site/components/InteractiveGraphics/InteractiveGraphics.tsx index de014e1..9f5f9ae 100644 --- a/site/components/InteractiveGraphics/InteractiveGraphics.tsx +++ b/site/components/InteractiveGraphics/InteractiveGraphics.tsx @@ -414,6 +414,7 @@ export const InteractiveGraphics = ({ const filterArrows = useFilterArrows({ isPointOnScreen, doesLineIntersectViewport, + filterLayerAndStep, }) const filterPolygons = useFilterPolygons({ isPointOnScreen, diff --git a/site/components/InteractiveGraphics/hooks/useFilterArrows.ts b/site/components/InteractiveGraphics/hooks/useFilterArrows.ts index 45ab73f..373c421 100644 --- a/site/components/InteractiveGraphics/hooks/useFilterArrows.ts +++ b/site/components/InteractiveGraphics/hooks/useFilterArrows.ts @@ -8,45 +8,67 @@ type LineCheck = (p1: Point, p2: Point) => boolean type PointCheck = (point: Point) => boolean +type LayerAndStepCheck = (obj: { layer?: string; step?: number }) => boolean + type UseFilterArrowsParams = { isPointOnScreen: PointCheck doesLineIntersectViewport: LineCheck + filterLayerAndStep: LayerAndStepCheck +} + +export const isArrowVisible = ( + arrow: Arrow, + { + isPointOnScreen, + doesLineIntersectViewport, + filterLayerAndStep, + }: UseFilterArrowsParams, +) => { + if (!filterLayerAndStep(arrow)) { + return false + } + + const geometry = getArrowGeometry(arrow) + const { shaftStart, shaftEnd, heads } = geometry + + if ( + isPointOnScreen(shaftStart) || + isPointOnScreen(shaftEnd) || + heads.some( + (head) => + isPointOnScreen(head.tip) || + isPointOnScreen(head.leftWing) || + isPointOnScreen(head.rightWing) || + isPointOnScreen(head.base), + ) + ) { + return true + } + + const segments: Array<[Point, Point]> = [ + [shaftStart, shaftEnd], + ...heads.flatMap((head) => [ + [head.base, head.leftWing] as [Point, Point], + [head.leftWing, head.tip] as [Point, Point], + [head.tip, head.rightWing] as [Point, Point], + [head.rightWing, head.base] as [Point, Point], + ]), + ] + + return segments.some(([p1, p2]) => doesLineIntersectViewport(p1, p2)) } export const useFilterArrows = ({ isPointOnScreen, doesLineIntersectViewport, + filterLayerAndStep, }: UseFilterArrowsParams) => { return useMemo(() => { - return (arrow: Arrow) => { - const geometry = getArrowGeometry(arrow) - const { shaftStart, shaftEnd, heads } = geometry - - if ( - isPointOnScreen(shaftStart) || - isPointOnScreen(shaftEnd) || - heads.some( - (head) => - isPointOnScreen(head.tip) || - isPointOnScreen(head.leftWing) || - isPointOnScreen(head.rightWing) || - isPointOnScreen(head.base), - ) - ) { - return true - } - - const segments: Array<[Point, Point]> = [ - [shaftStart, shaftEnd], - ...heads.flatMap((head) => [ - [head.base, head.leftWing] as [Point, Point], - [head.leftWing, head.tip] as [Point, Point], - [head.tip, head.rightWing] as [Point, Point], - [head.rightWing, head.base] as [Point, Point], - ]), - ] - - return segments.some(([p1, p2]) => doesLineIntersectViewport(p1, p2)) - } - }, [isPointOnScreen, doesLineIntersectViewport]) + return (arrow: Arrow) => + isArrowVisible(arrow, { + isPointOnScreen, + doesLineIntersectViewport, + filterLayerAndStep, + }) + }, [isPointOnScreen, doesLineIntersectViewport, filterLayerAndStep]) } diff --git a/tests/use-filter-arrows.test.ts b/tests/use-filter-arrows.test.ts new file mode 100644 index 0000000..c309332 --- /dev/null +++ b/tests/use-filter-arrows.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from "bun:test" +import type { Arrow } from "lib/types" +import { isArrowVisible } from "site/components/InteractiveGraphics/hooks/useFilterArrows" + +const visibleArrow: Arrow = { + start: { x: 0, y: 0 }, + end: { x: 10, y: 0 }, + layer: "top", + step: 2, +} + +test("isArrowVisible excludes arrows rejected by layer and step filters", () => { + const isVisible = isArrowVisible(visibleArrow, { + isPointOnScreen: () => true, + doesLineIntersectViewport: () => true, + filterLayerAndStep: () => false, + }) + + expect(isVisible).toBe(false) +}) + +test("isArrowVisible keeps arrows that pass layer and step filters", () => { + const isVisible = isArrowVisible(visibleArrow, { + isPointOnScreen: () => true, + doesLineIntersectViewport: () => false, + filterLayerAndStep: () => true, + }) + + expect(isVisible).toBe(true) +})