Orphan discs in regions + orphan exemption from depth anchoring
Two follow-ups on the semantic-cluster wave, both user-reported: 1. Region coloring: edge-only dilation (the anti-double-counting rule from the Louvain era) left 114 of 338 cluster members completely uncolored — semantic clusters contain members that share no intra-cluster edge with anyone (90 edge-orphans + isolated micro-topics), and 20 of 45 clusters painted nothing at all. Fix: a disc (radius = dilation, same as a round cap) for exactly the members no intra-cluster edge stroke reached. Every member is painted by its edges' caps OR one disc, never both — the no-double-counting rule survives. Verified live: all 74 orphans-in-clusters sit on tinted region pixels. 2. Depth modes (ORPHAN RULE): a node with no edges has no dependency structure, so depth-0 stamping put 90 unconnected propositions on the vertical baseline / radial base ring — meaning they don't have. Orphans are now exempt from the depth force and get the regular view's unprojected forces instead (full center gravity + full cluster pull). Structured-node projections unchanged (x-only gravity in vertical, none in radial; tangential-only cluster pull in radial). Gravity in depth modes is a custom force with EXACTLY d3's forceX/forceY math (strength × alpha — the first draft forgot the alpha and shifted the settle). Verified live: radial orphans follow their topics instead of the base ring; vertical orphans spread 1006 world units (6/90 near the baseline band vs 90/90 before).
This commit is contained in:
@@ -1205,10 +1205,9 @@ export class GraphCanvasRenderer {
|
||||
});
|
||||
|
||||
// The region IS the dilated skeleton: intra-cluster edges as
|
||||
// round-cap strokes (Minkowski dilation — points become discs at
|
||||
// line ends via the caps). NO per-node discs: a cluster is its
|
||||
// connected points, and discs double-counted where nodes sit on
|
||||
// edges (user-identified hot spots).
|
||||
// round-cap strokes (Minkowski dilation). NO blanket per-node discs
|
||||
// — they double-counted where nodes sit on edges (user-identified
|
||||
// hot spots, when clusters were edge-based).
|
||||
this.regionMeta = [];
|
||||
data.clusters.forEach((cluster, ci) => {
|
||||
const hue = clusterHue(cluster.id);
|
||||
@@ -1217,6 +1216,8 @@ export class GraphCanvasRenderer {
|
||||
let members = 0;
|
||||
let sumX = 0;
|
||||
let sumY = 0;
|
||||
// Endpoints actually covered by a stroke's round caps.
|
||||
const touched = new Set<string>();
|
||||
for (const e of data.edges) {
|
||||
if (clusterOf.get(e.from) !== ci) continue;
|
||||
if (clusterOf.get(e.to) !== ci) continue;
|
||||
@@ -1225,9 +1226,29 @@ export class GraphCanvasRenderer {
|
||||
if (!a || !b) continue;
|
||||
rctx.moveTo((a.x - minX) / cell, (a.y - minY) / cell);
|
||||
rctx.lineTo((b.x - minX) / cell, (b.y - minY) / cell);
|
||||
touched.add(e.from);
|
||||
touched.add(e.to);
|
||||
}
|
||||
rctx.stroke();
|
||||
|
||||
// Member discs for the SEMANTIC era (DECIDED 2026-09-13): clusters
|
||||
// are now topics by meaning, and many members (orphans, isolated
|
||||
// micro-topics) share NO intra-cluster edge with anyone — edge
|
||||
// strokes alone left them completely uncolored. A disc (same
|
||||
// radius as a round cap = dilation) covers exactly the members no
|
||||
// stroke reached: every member is painted by its edges' caps OR
|
||||
// one disc, never both — the no-double-counting rule survives.
|
||||
rctx.fillStyle = `hsla(${hue}, 65%, 60%, ${tuning.edgeAlpha})`;
|
||||
const discR = (tuning.dilation * s) / cell;
|
||||
for (const id of cluster.members) {
|
||||
if (touched.has(id) || !isVisible(id)) continue;
|
||||
const n = this.nodeById.get(id);
|
||||
if (!n) continue;
|
||||
rctx.beginPath();
|
||||
rctx.arc((n.x - minX) / cell, (n.y - minY) / cell, discR, 0, Math.PI * 2);
|
||||
rctx.fill();
|
||||
}
|
||||
|
||||
// Label anchor: member centroid (world coords).
|
||||
for (const id of cluster.members) {
|
||||
if (!isVisible(id)) continue;
|
||||
|
||||
Reference in New Issue
Block a user