Re-tune render sizes for the world-space model: smaller defaults, wide low ends
The defaults were calibrated in the screen-fixed era; world-scaling renders
sizes at setting x k/kFit, so at the default label gate (2x fit) everything
read ~2x too big ("labels way too large even at the minimum"). Fit-zoom
normalizes layout scale away, so the fix belongs in sizes, not layout:
spreading nodes further apart in world units changes nothing at fit.
- Defaults: nodeSize 6 -> 4, arrowLength 5 -> 4, labelSize 12 -> 7.
- Clamps/slider ranges widened at the low end: nodeSize 1-14 (step 0.5),
edgeWidth 0.25-4 (step 0.25), arrowLength 1-15 (step 0.5),
labelSize 3-24 (step 0.5).
- GLOW_PAD 9 -> 6 (halo dominated small discs), REGION_LABEL_SIZE 18 -> 14.
- Tests updated for the new clamp bounds plus a low-end pass-through case
(403/403). The saved render-settings.json still shadows defaults by
design; the local copy was reset so the new defaults showed immediately.
This commit is contained in:
@@ -117,7 +117,7 @@ const LABEL_LINE_HEIGHT = 1.3;
|
|||||||
/** Zoom span (camera-ratio units) over which labels fade together. */
|
/** Zoom span (camera-ratio units) over which labels fade together. */
|
||||||
export const LABELS_FADE_SPAN = 0.5;
|
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 GLOW_ALPHA = 0.45; // halo opacity at the disc edge, fading outward
|
||||||
const HOVER_FADE_MS = 180; // hover halo opacity fade length
|
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)
|
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;
|
] as const;
|
||||||
const HULL_STROKE_PAD = 14;
|
const HULL_STROKE_PAD = 14;
|
||||||
const HULL_CORNER_RADIUS = 9;
|
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
|
/** 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
|
* them into world units (÷ kFit) once per geometry build; camera zoom then
|
||||||
|
|||||||
@@ -500,33 +500,33 @@ export function GraphView() {
|
|||||||
<div className="mt-1.5 flex flex-col gap-2 border-t pt-1.5">
|
<div className="mt-1.5 flex flex-col gap-2 border-t pt-1.5">
|
||||||
<SettingSlider
|
<SettingSlider
|
||||||
label="Node size"
|
label="Node size"
|
||||||
min={2}
|
min={1}
|
||||||
max={14}
|
max={14}
|
||||||
step={1}
|
step={0.5}
|
||||||
value={settings.nodeSize}
|
value={settings.nodeSize}
|
||||||
onChange={(v) => changeSettings((cur) => ({ ...cur, nodeSize: v }))}
|
onChange={(v) => changeSettings((cur) => ({ ...cur, nodeSize: v }))}
|
||||||
/>
|
/>
|
||||||
<SettingSlider
|
<SettingSlider
|
||||||
label="Edge width"
|
label="Edge width"
|
||||||
min={0.5}
|
min={0.25}
|
||||||
max={4}
|
max={4}
|
||||||
step={0.5}
|
step={0.25}
|
||||||
value={settings.edgeWidth}
|
value={settings.edgeWidth}
|
||||||
onChange={(v) => changeSettings((cur) => ({ ...cur, edgeWidth: v }))}
|
onChange={(v) => changeSettings((cur) => ({ ...cur, edgeWidth: v }))}
|
||||||
/>
|
/>
|
||||||
<SettingSlider
|
<SettingSlider
|
||||||
label="Arrowhead size"
|
label="Arrowhead size"
|
||||||
min={2}
|
min={1}
|
||||||
max={15}
|
max={15}
|
||||||
step={1}
|
step={0.5}
|
||||||
value={settings.arrowLength}
|
value={settings.arrowLength}
|
||||||
onChange={(v) => changeSettings((cur) => ({ ...cur, arrowLength: v }))}
|
onChange={(v) => changeSettings((cur) => ({ ...cur, arrowLength: v }))}
|
||||||
/>
|
/>
|
||||||
<SettingSlider
|
<SettingSlider
|
||||||
label="Label size"
|
label="Label size"
|
||||||
min={6}
|
min={3}
|
||||||
max={24}
|
max={24}
|
||||||
step={1}
|
step={0.5}
|
||||||
value={settings.labelSize}
|
value={settings.labelSize}
|
||||||
onChange={(v) => changeSettings((cur) => ({ ...cur, labelSize: v }))}
|
onChange={(v) => changeSettings((cur) => ({ ...cur, labelSize: v }))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -22,18 +22,23 @@ export interface GraphRenderSettings {
|
|||||||
arrowLength: number;
|
arrowLength: number;
|
||||||
/** Camera-ratio threshold for node labels (see module doc). */
|
/** Camera-ratio threshold for node labels (see module doc). */
|
||||||
labelZoom: number;
|
labelZoom: number;
|
||||||
/** Node + hover label font size, px. */
|
/** Node + hover label font size, px at fit zoom (scales with camera). */
|
||||||
labelSize: number;
|
labelSize: number;
|
||||||
/** Toggle for the cluster region labels (hulls always draw). */
|
/** Toggle for the cluster region labels (hulls always draw). */
|
||||||
clusterLabels: boolean;
|
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 = {
|
export const DEFAULT_RENDER_SETTINGS: GraphRenderSettings = {
|
||||||
nodeSize: 6,
|
nodeSize: 4,
|
||||||
edgeWidth: 1,
|
edgeWidth: 1,
|
||||||
arrowLength: 5,
|
arrowLength: 4,
|
||||||
labelZoom: 0.5,
|
labelZoom: 0.5,
|
||||||
labelSize: 12,
|
labelSize: 7,
|
||||||
clusterLabels: true,
|
clusterLabels: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,12 +56,12 @@ export function sanitizeRenderSettings(raw: unknown): GraphRenderSettings {
|
|||||||
return clamp(n, min, max);
|
return clamp(n, min, max);
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
nodeSize: num(r.nodeSize, DEFAULT_RENDER_SETTINGS.nodeSize, 2, 14),
|
nodeSize: num(r.nodeSize, DEFAULT_RENDER_SETTINGS.nodeSize, 1, 14),
|
||||||
edgeWidth: num(r.edgeWidth, DEFAULT_RENDER_SETTINGS.edgeWidth, 0.5, 4),
|
edgeWidth: num(r.edgeWidth, DEFAULT_RENDER_SETTINGS.edgeWidth, 0.25, 4),
|
||||||
arrowLength: num(r.arrowLength, DEFAULT_RENDER_SETTINGS.arrowLength, 2, 15),
|
arrowLength: num(r.arrowLength, DEFAULT_RENDER_SETTINGS.arrowLength, 1, 15),
|
||||||
// Range matches the log slider's span [LABEL_ZOOM_MIN, LABEL_ZOOM_MAX].
|
// Range matches the log slider's span [LABEL_ZOOM_MIN, LABEL_ZOOM_MAX].
|
||||||
labelZoom: num(r.labelZoom, DEFAULT_RENDER_SETTINGS.labelZoom, 0.0625, 4),
|
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:
|
clusterLabels:
|
||||||
typeof r.clusterLabels === "boolean" ? r.clusterLabels : DEFAULT_RENDER_SETTINGS.clusterLabels,
|
typeof r.clusterLabels === "boolean" ? r.clusterLabels : DEFAULT_RENDER_SETTINGS.clusterLabels,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,13 +44,21 @@ describe("sanitizeRenderSettings", () => {
|
|||||||
clusterLabels: "yes" as unknown as boolean,
|
clusterLabels: "yes" as unknown as boolean,
|
||||||
});
|
});
|
||||||
assert.equal(s.nodeSize, 14); // clamped to max
|
assert.equal(s.nodeSize, 14); // clamped to max
|
||||||
assert.equal(s.edgeWidth, 0.5); // clamped to min
|
assert.equal(s.edgeWidth, 0.25); // clamped to min
|
||||||
assert.equal(s.arrowLength, 2);
|
assert.equal(s.arrowLength, 1);
|
||||||
assert.equal(s.labelZoom, LABEL_ZOOM_MAX);
|
assert.equal(s.labelZoom, LABEL_ZOOM_MAX);
|
||||||
assert.equal(s.labelSize, DEFAULT_RENDER_SETTINGS.labelSize); // NaN-safe reset
|
assert.equal(s.labelSize, DEFAULT_RENDER_SETTINGS.labelSize); // NaN-safe reset
|
||||||
assert.equal(s.clusterLabels, DEFAULT_RENDER_SETTINGS.clusterLabels);
|
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", () => {
|
test("a fully valid object passes through unchanged", () => {
|
||||||
const valid = { nodeSize: 8, edgeWidth: 2, arrowLength: 7, labelZoom: 0.25, labelSize: 16, clusterLabels: false };
|
const valid = { nodeSize: 8, edgeWidth: 2, arrowLength: 7, labelZoom: 0.25, labelSize: 16, clusterLabels: false };
|
||||||
assert.deepEqual(sanitizeRenderSettings(valid), valid);
|
assert.deepEqual(sanitizeRenderSettings(valid), valid);
|
||||||
|
|||||||
Reference in New Issue
Block a user