Merge pull request 'fix(graph): stop corpus-graph labels overlapping' (#118) from worktree-corpus-graph-labels into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m1s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m1s
This commit was merged in pull request #118.
This commit is contained in:
@@ -130,6 +130,17 @@ export function GraphCanvas({
|
||||
fgRef.current?.zoomToFit?.(400, 60);
|
||||
}, []);
|
||||
|
||||
// Spread nodes out so labels have room — stronger repulsion + longer links
|
||||
// than the d3-force defaults (charge -30, link 30). Re-applied whenever the
|
||||
// graph data changes (full ↔ neighborhood).
|
||||
useEffect(() => {
|
||||
const fg = fgRef.current;
|
||||
if (!fg?.d3Force) return;
|
||||
fg.d3Force("charge")?.strength?.(-220);
|
||||
fg.d3Force("link")?.distance?.(60);
|
||||
fg.d3ReheatSimulation?.();
|
||||
}, [graphData]);
|
||||
|
||||
const drawNode = useCallback(
|
||||
(node: FGNode, ctx: CanvasRenderingContext2D, globalScale: number) => {
|
||||
const r = nodeRadius(node);
|
||||
@@ -147,22 +158,39 @@ export function GraphCanvas({
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Labels: hubs always; precedents when zoomed in, important, or active.
|
||||
const isHub = node.type === "topic" || node.type === "practice_area";
|
||||
// Labels — gated by zoom so they don't pile up. At overview zoom only a
|
||||
// few show (active node, the 3 practice-area hubs, the most-cited
|
||||
// precedents); zooming in reveals the rest, by which point there is pixel
|
||||
// room between nodes. Font is kept at a ~constant SCREEN size (px /
|
||||
// globalScale) instead of growing as you zoom out — that growth was what
|
||||
// made labels collide.
|
||||
const isTopicHub = node.type === "topic";
|
||||
const isAreaHub = node.type === "practice_area";
|
||||
const showLabel =
|
||||
!dimmed &&
|
||||
(isHub || node.id === activeId || node.size >= 3 || globalScale >= 1.6);
|
||||
(node.id === activeId ||
|
||||
isAreaHub ||
|
||||
globalScale >= 1.5 ||
|
||||
(isTopicHub && globalScale >= 1.05) ||
|
||||
(!isTopicHub && node.size >= 4 && globalScale >= 0.9));
|
||||
if (showLabel && node.label) {
|
||||
const fontSize = Math.max(2.5, (isHub ? 4.5 : 3.6) / Math.sqrt(globalScale)) +
|
||||
(isHub ? 1 : 0);
|
||||
ctx.font = `${fontSize + 6}px Heebo, sans-serif`;
|
||||
const fontPx = isTopicHub || isAreaHub ? 12 : 11;
|
||||
const fontSize = fontPx / globalScale; // ~constant on screen
|
||||
ctx.font = `${fontSize}px Heebo, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.direction = "rtl";
|
||||
ctx.fillStyle = isHub ? "#7a5a26" : "#1a1a2e";
|
||||
const label =
|
||||
node.label.length > 28 ? `${node.label.slice(0, 27)}…` : node.label;
|
||||
ctx.fillText(label, node.x ?? 0, (node.y ?? 0) + r + 1);
|
||||
node.label.length > 22 ? `${node.label.slice(0, 21)}…` : node.label;
|
||||
const lx = node.x ?? 0;
|
||||
const ly = (node.y ?? 0) + r + 2 / globalScale;
|
||||
// White halo so the text stays readable over edges and nearby nodes.
|
||||
ctx.lineWidth = 3 / globalScale;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.92)";
|
||||
ctx.lineJoin = "round";
|
||||
ctx.strokeText(label, lx, ly);
|
||||
ctx.fillStyle = isTopicHub || isAreaHub ? "#7a5a26" : "#1a1a2e";
|
||||
ctx.fillText(label, lx, ly);
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user