/** * Pure layout for node labels. * * Generates the callout slot geometry (corner, leader line endpoints, * text anchor) and the placed label rect. The render component consumes * a `Corner` from `computeNodeLabelRect` and re-runs `computeCallout` * to get the leader line + text positions. * * Kept React-free so it can be tested without a renderer. */ import type { NormalizedNodeDef } from '../types.js'; import type { GridLayout } from '../geometry/grid.js'; import type { LabelRect, LineSeg, CanvasBounds, Circle } from '../geometry/collision.js'; import { type AttemptRecord } from '../geometry/label-placer.js'; export type Corner = 'top-right' | 'bottom-right' | 'bottom-left' | 'top-left' | 'top-center' | 'bottom-center' | 'left-center' | 'right-center'; export interface CalloutSlot { dx: number; dy: number; anchor: 'start' | 'middle' | 'end'; } export declare const SLOTS: Record; /** * Candidate order. The 4 diagonal corners remain primary (tried first, CW * starting from TR). TC/BC are tried next; LC/RC come after the up/down * fallbacks so labels prefer above/below before sliding to the side. */ export declare const CORNER_ORDER: Corner[]; /** * Leader-gap multipliers for the tiered search. Tier 1 is the tight * default; tiers 2 and 3 pull the label progressively further from the * node so crowded cells have somewhere to put the callout. Each tier * walks all six corners in CORNER_ORDER before the next tier begins. * If none fit cleanly, the placer falls back to the candidate with the * smallest collision (see `placeLabel`'s 'smallest-collision' strategy). */ export declare const LEADER_TIERS: readonly [1, 2, 3]; export interface CalloutGeometry { labelRect: LabelRect; edgeX: number; edgeY: number; textX: number; textY: number; leaderTargetX: number; leaderTargetY: number; anchor: 'start' | 'middle' | 'end'; } export declare function computeCallout(x: number, y: number, half: number, slot: CalloutSlot, fontSize: number, textW: number, textH: number, leaderGap: number): CalloutGeometry; export interface NodeLabelMetrics { x: number; y: number; half: number; fs: number; leaderGap: number; textW: number; textH: number; } /** Resolve the geometry inputs needed to position a node label. */ export declare function nodeLabelMetrics(node: NormalizedNodeDef, layout: GridLayout): NodeLabelMetrics; export interface NodeLabelResult { rect: LabelRect; corner: Corner; /** Leader-length tier (1 = tight, 2 = medium, 3 = long) the placer * chose for this label. The renderer multiplies its base leaderGap * by this value when drawing the callout, so tier-2 / tier-3 wins * actually appear at their longer offsets. */ tier: number; error: boolean; /** Every corner the placer tried, for diagnostic emission. */ attempts: AttemptRecord[]; } /** * Pre-compute node label placement. * * Candidate order: * tier 1 (leader×1): TR → BR → BL → TL → TC → BC * tier 2 (leader×2): TR → BR → BL → TL → TC → BC * tier 3 (leader×3): TR → BR → BL → TL → TC → BC * * 18 positions total. If every tier collides, the placer picks the * candidate with the fewest collision hits (tie-break: earliest in the * list, i.e. tier-1 wins over tier-2 wins over tier-3 on ties), so the * label degrades gracefully instead of always landing in one forced * corner. Slot is still returned as a plain `Corner` — the tier is * encoded only in the attempt description for diagnostics. */ export declare function computeNodeLabelRect(node: NormalizedNodeDef, layout: GridLayout, placedLabels: LabelRect[], connLines: LineSeg[], bounds?: CanvasBounds, iconCircles?: Circle[]): NodeLabelResult | null; //# sourceMappingURL=node-label.d.ts.map