import * as d3 from 'd3'; import { linkSegmentThroughNodeRects } from './genAttributeDagLinkSegment'; type TextFlowNodeLike = { /** 测量层矩形左上角(1×;compactness / CI 只影响绘制偏移与尺寸) */ x: number; y: number; nodeW: number; nodeH: number; ciVisualScale: number; }; /** 节点在 rootG 下的绘制位姿(translate 左上角;可选统一 scale)。 */ export type DagNodeLayoutPose = { id: string; x: number; y: number; nodeW: number; nodeH: number; /** 相对节点本地原点的额外 scale;默认 1 */ scale: number; }; /** 位姿 → SVG transform(含 spiral 首节点的 scale 中心补偿)。 */ export function poseToTransform(p: DagNodeLayoutPose): string { if (p.scale !== 1) { return `translate(${p.x},${p.y}) scale(${p.scale}) translate(${-p.nodeW / 2},${-p.nodeH / 2})`; } return `translate(${p.x},${p.y})`; } /** 剥掉 CI 后的 compactness 底尺寸。 */ export function dagNodeBaseSize(d: Pick): { baseW: number; baseH: number; } { return { baseW: d.nodeW / d.ciVisualScale, baseH: d.nodeH / d.ciVisualScale }; } /** {@link dagNodeBaseSize}.baseW 的简写(子词拼接等)。 */ export function dagNodeBaseWidth(d: Pick): number { return d.nodeW / d.ciVisualScale; } /** * text-flow 绘制用左上角: * - compactness:在测量槽内竖直居中(水平仍贴测量左缘) * - CI:相对 compactness 框中心对称外扩 */ export function dagTextFlowPaintOrigin( d: TextFlowNodeLike, displayScale: number, ): { x: number; y: number } { const { baseW, baseH } = dagNodeBaseSize(d); const measureH = baseH / displayScale; return { x: d.x - (d.nodeW - baseW) / 2, y: d.y + (measureH - d.nodeH) / 2, }; } /** text-flow:各节点绘制 translate(左上角)。 */ export function computeTextFlowNodeRects( nodes: Array, displayScale: number, ): Map { const out = new Map(); for (const d of nodes) { const p = dagTextFlowPaintOrigin(d, displayScale); out.set(d.id, { id: d.id, x: p.x, y: p.y, nodeW: d.nodeW, nodeH: d.nodeH, scale: 1, }); } return out; } /** text-flow 模式:节点锚测量左上角,绘制时补偿 compactness / CI。 */ export function paintTextFlowLayout< LinkDatum, NodeDatum extends TextFlowNodeLike & { id: string }, >(params: { linkSel: d3.Selection; nodeSel: d3.Selection; linkEndInsetPx: number; displayScale: number; getLinkNodes: (link: LinkDatum) => { src: NodeDatum; tgt: NodeDatum }; }): void { const { linkSel, nodeSel, linkEndInsetPx, displayScale, getLinkNodes } = params; const poses = computeTextFlowNodeRects(nodeSel.data(), displayScale); linkSel.each(function (d) { const { src, tgt } = getLinkNodes(d); const ps = poses.get(src.id)!; const pt = poses.get(tgt.id)!; const seg = linkSegmentThroughNodeRects( { cx: ps.x + src.nodeW / 2, cy: ps.y + src.nodeH / 2, nodeW: src.nodeW, nodeH: src.nodeH }, { cx: pt.x + tgt.nodeW / 2, cy: pt.y + tgt.nodeH / 2, nodeW: tgt.nodeW, nodeH: tgt.nodeH }, linkEndInsetPx, ); d3.select(this) .selectAll('path.gen-attr-dag-link-visible') .attr('d', `M ${seg.x1} ${seg.y1} L ${seg.x2} ${seg.y2}`); }); nodeSel.attr('transform', (d) => { const p = poses.get(d.id); if (!p) return null; return `translate(${p.x},${p.y})`; }); }