diff --git a/components/graph-canvas.ts b/components/graph-canvas.ts index 285437b..5007412 100644 --- a/components/graph-canvas.ts +++ b/components/graph-canvas.ts @@ -117,7 +117,7 @@ const LABEL_LINE_HEIGHT = 1.3; /** Zoom span (camera-ratio units) over which labels fade together. */ export const LABELS_FADE_SPAN = 0.5; -const GLOW_PAD = 9; // px the halo extends beyond the node disc (constant) +const GLOW_PAD = 6; // px the halo extends beyond the node disc (constant) const GLOW_ALPHA = 0.45; // halo opacity at the disc edge, fading outward const HOVER_FADE_MS = 180; // hover halo opacity fade length const ZOOM_EASE_MS = 45; // wheel-zoom glide: exponential-ease time constant (~250ms per notch) @@ -139,7 +139,7 @@ const HULL_FILL_LAYERS = [ ] as const; const HULL_STROKE_PAD = 14; const HULL_CORNER_RADIUS = 9; -const REGION_LABEL_SIZE = 18; +const REGION_LABEL_SIZE = 14; /** All "px" constants above are screen px AT FIT ZOOM. The renderer scales * them into world units (รท kFit) once per geometry build; camera zoom then diff --git a/components/graph-view.tsx b/components/graph-view.tsx index b27c485..54d4a4b 100644 --- a/components/graph-view.tsx +++ b/components/graph-view.tsx @@ -500,33 +500,33 @@ export function GraphView() {
changeSettings((cur) => ({ ...cur, nodeSize: v }))} /> changeSettings((cur) => ({ ...cur, edgeWidth: v }))} /> changeSettings((cur) => ({ ...cur, arrowLength: v }))} /> changeSettings((cur) => ({ ...cur, labelSize: v }))} /> diff --git a/src/core/render-settings.ts b/src/core/render-settings.ts index 8946890..0ca1e2b 100644 --- a/src/core/render-settings.ts +++ b/src/core/render-settings.ts @@ -22,18 +22,23 @@ export interface GraphRenderSettings { arrowLength: number; /** Camera-ratio threshold for node labels (see module doc). */ labelZoom: number; - /** Node + hover label font size, px. */ + /** Node + hover label font size, px at fit zoom (scales with camera). */ labelSize: number; /** Toggle for the cluster region labels (hulls always draw). */ clusterLabels: boolean; } +// Sizes are screen px AT FIT ZOOM (the world-space renderer scales them +// with the camera, so a label reads at labelSize x k/kFit on screen). +// Defaults were re-tuned for world-scaling on 2026-09-13: the old values +// were calibrated in the screen-fixed era, so everything rendered ~2x +// too big once sizes started scaling with the graph. export const DEFAULT_RENDER_SETTINGS: GraphRenderSettings = { - nodeSize: 6, + nodeSize: 4, edgeWidth: 1, - arrowLength: 5, + arrowLength: 4, labelZoom: 0.5, - labelSize: 12, + labelSize: 7, clusterLabels: true, }; @@ -51,12 +56,12 @@ export function sanitizeRenderSettings(raw: unknown): GraphRenderSettings { return clamp(n, min, max); }; return { - nodeSize: num(r.nodeSize, DEFAULT_RENDER_SETTINGS.nodeSize, 2, 14), - edgeWidth: num(r.edgeWidth, DEFAULT_RENDER_SETTINGS.edgeWidth, 0.5, 4), - arrowLength: num(r.arrowLength, DEFAULT_RENDER_SETTINGS.arrowLength, 2, 15), + nodeSize: num(r.nodeSize, DEFAULT_RENDER_SETTINGS.nodeSize, 1, 14), + edgeWidth: num(r.edgeWidth, DEFAULT_RENDER_SETTINGS.edgeWidth, 0.25, 4), + arrowLength: num(r.arrowLength, DEFAULT_RENDER_SETTINGS.arrowLength, 1, 15), // Range matches the log slider's span [LABEL_ZOOM_MIN, LABEL_ZOOM_MAX]. labelZoom: num(r.labelZoom, DEFAULT_RENDER_SETTINGS.labelZoom, 0.0625, 4), - labelSize: num(r.labelSize, DEFAULT_RENDER_SETTINGS.labelSize, 6, 24), + labelSize: num(r.labelSize, DEFAULT_RENDER_SETTINGS.labelSize, 3, 24), clusterLabels: typeof r.clusterLabels === "boolean" ? r.clusterLabels : DEFAULT_RENDER_SETTINGS.clusterLabels, }; diff --git a/tests/render-settings.test.ts b/tests/render-settings.test.ts index 55d2b5c..7cddeef 100644 --- a/tests/render-settings.test.ts +++ b/tests/render-settings.test.ts @@ -44,13 +44,21 @@ describe("sanitizeRenderSettings", () => { clusterLabels: "yes" as unknown as boolean, }); assert.equal(s.nodeSize, 14); // clamped to max - assert.equal(s.edgeWidth, 0.5); // clamped to min - assert.equal(s.arrowLength, 2); + assert.equal(s.edgeWidth, 0.25); // clamped to min + assert.equal(s.arrowLength, 1); assert.equal(s.labelZoom, LABEL_ZOOM_MAX); assert.equal(s.labelSize, DEFAULT_RENDER_SETTINGS.labelSize); // NaN-safe reset assert.equal(s.clusterLabels, DEFAULT_RENDER_SETTINGS.clusterLabels); }); + test("the widened low ends accept small sizes", () => { + const s = sanitizeRenderSettings({ nodeSize: 1, edgeWidth: 0.25, arrowLength: 1, labelSize: 3 }); + assert.deepEqual( + { nodeSize: s.nodeSize, edgeWidth: s.edgeWidth, arrowLength: s.arrowLength, labelSize: s.labelSize }, + { nodeSize: 1, edgeWidth: 0.25, arrowLength: 1, labelSize: 3 }, + ); + }); + test("a fully valid object passes through unchanged", () => { const valid = { nodeSize: 8, edgeWidth: 2, arrowLength: 7, labelZoom: 0.25, labelSize: 16, clusterLabels: false }; assert.deepEqual(sanitizeRenderSettings(valid), valid);