Cluster labels: topmost layer + user-facing size setting

- Draw order: the cluster-label block moves from layer 5 to THE LAST
  layer of draw() — labels can never be occluded by node labels or
  hover names (user 2026-09-17; chose "topmost" after reassessing that
  discs were never actually on top — the white-out over discs is
  source-over compositing of the 85%-alpha text over near-white node
  fill, an optical contrast flip, not a draw-order bug).
- clusterLabelSize: new persisted setting (default 14 = the old
  REGION_LABEL_SIZE constant, so rendering is pixel-identical). Screen
  px at ANY zoom (drawn at size/k — camera-compensated, UI-like, same
  law as before). Log-slider range [7, 28] puts the default dead-center
  per the house slider law. Sanitized: old persisted files (no field)
  fall back to 14.
- Panel: the Cluster labels checkbox moves from the Labels section
  into Regions (user: cluster settings live with regions), joined by
  the new "Cluster label size" slider. Labels stay decoupled from the
  Background colors compute switch, as designed on 2026-09-16.
This commit is contained in:
amosblomqvist
2026-09-17 09:35:11 +03:00
parent b24bce3f83
commit 2f4b9c82e4
4 changed files with 80 additions and 28 deletions
+14
View File
@@ -34,6 +34,10 @@ export interface GraphRenderSettings {
labelSize: number;
/** Toggle for the cluster region labels (hulls always draw). */
clusterLabels: boolean;
/** Cluster label font size, px on screen (camera-compensated —
* constant at any zoom, unlike labelSize which scales with the
* graph). Defaults to the old REGION_LABEL_SIZE constant (14). */
clusterLabelSize: number;
/** Region rendering (the blurred cluster tints). Server-persisted
* since 2026-09-13 (user request) — was session-only dev state. */
regions: RegionTuning;
@@ -65,6 +69,12 @@ export function sanitizeRenderSettings(raw: unknown): GraphRenderSettings {
labelSize: num(r.labelSize, DEFAULT_RENDER_SETTINGS.labelSize, LABEL_SIZE_RANGE.min, LABEL_SIZE_RANGE.max),
clusterLabels:
typeof r.clusterLabels === "boolean" ? r.clusterLabels : DEFAULT_RENDER_SETTINGS.clusterLabels,
clusterLabelSize: num(
r.clusterLabelSize,
DEFAULT_RENDER_SETTINGS.clusterLabelSize,
CLUSTER_LABEL_SIZE_RANGE.min,
CLUSTER_LABEL_SIZE_RANGE.max,
),
// Nested sections (2026-09-13): old files lack them → defaults.
regions: sanitizeRegionTuning(r.regions),
physics: sanitizePhysicsTuning(r.physics),
@@ -214,6 +224,7 @@ export const DEFAULT_RENDER_SETTINGS: GraphRenderSettings = {
labelZoom: 0.5,
labelSize: 2,
clusterLabels: true,
clusterLabelSize: 14,
regions: DEFAULT_REGION_TUNING,
physics: DEFAULT_PHYSICS_TUNING,
};
@@ -229,6 +240,9 @@ export const NODE_SIZE_RANGE = { min: 0.5, max: 8 } as const;
export const EDGE_WIDTH_RANGE = { min: 0.25, max: 4 } as const;
export const ARROW_LENGTH_RANGE = { min: 0.5, max: 8 } as const;
export const LABEL_SIZE_RANGE = { min: 0.5, max: 8 } as const;
// Screen-px at ANY zoom (drawn at size / k). 14 = geometric mean — old
// REGION_LABEL_SIZE default, so the panel slider starts dead-center.
export const CLUSTER_LABEL_SIZE_RANGE = { min: 7, max: 28 } as const;
export const LABEL_ZOOM_MIN = 0.0625; // slider far left: labels only very deep in
export const LABEL_ZOOM_MAX = 4; // slider far right: labels visible at fit zoom