@fortemi/graph 2026.6.8 → 2026.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ Rendering a knowledge graph usually means re-implementing layout math, community
35
35
 
36
36
  | Need | What Fortemi Graph provides |
37
37
  |---|---|
38
- | Deterministic layout | `layoutCommunityGraph` — closed-form `force`/`radial`/`community`/`manual` positions, no randomness |
38
+ | Deterministic layout | `layoutCommunityGraph` — a seeded `force` settlement plus closed-form `radial`/`community`/`manual` positions; identical input always yields identical coordinates |
39
39
  | Visibility control | `filterCommunityGraph` — filter by community, edge kind, node allow-list, or predicate |
40
40
  | Readable encoding | Degree-based node sizing and deterministic community color assignment |
41
41
  | Viewport math | Bounding box plus a centered fit transform for SVG/canvas |
@@ -114,7 +114,7 @@ These shapes are structurally identical to the ones `@fortemi/core` produces (`G
114
114
 
115
115
  | Helper | Purpose |
116
116
  |---|---|
117
- | `layoutCommunityGraph(graph, opts?)` | Deterministic 2D positions (`force`/`radial`/`community`/`manual`) plus per-node degree and community |
117
+ | `layoutCommunityGraph(graph, opts?)` | Deterministic 2D positions — a seeded `force` settlement (link attraction, charge repulsion, collision spacing, community cohesion, centering, bounds clamping) or closed-form `radial`/`community`/`manual`. Returns per-node `x, y, r`, degree, community, plus community centroids |
118
118
  | `filterCommunityGraph(graph, filter?)` | Filter by community, edge kind, node allow-list, or predicate; drops emptied communities |
119
119
  | `computeDegrees(graph)` / `nodeRadius(degree, opts?)` | Undirected degree map and degree → render radius |
120
120
  | `colorForCommunity(id, palette?)` | Deterministic community → color (themeable palette) |
@@ -122,6 +122,25 @@ These shapes are structurally identical to the ones `@fortemi/core` produces (`G
122
122
  | `neighborsOf` / `expandNeighborhood` / `subgraphForNodes` / `neighborhoodSubgraph` / `buildAdjacency` | Selection and BFS neighborhood expansion |
123
123
  | `serializeGraphSnapshot` / `stringifyGraphSnapshot` / `deserializeGraphSnapshot` | Stable, reproducible static snapshots for JS-only hosts |
124
124
 
125
+ ### Layout options (`force`)
126
+
127
+ The `force` algorithm runs a fixed-iteration, seeded settlement — fully synchronous and headless (no animation frames), so identical `(graph, options)` always yields identical coordinates. Suitable for static SVG generation, SSR, and browser rendering. The `radial`, `community`, and `manual` algorithms are closed-form and honor `width`/`height`/`boundsPadding` only.
128
+
129
+ | Option | Default | Purpose |
130
+ |---|---|---|
131
+ | `algorithm` | `'force'` | `force` (settlement) · `radial` · `community` · `manual` (closed-form) |
132
+ | `width` / `height` | `760` / `460` | Canvas size |
133
+ | `seed` | `1` | PRNG seed for initial jitter (identical seed ⇒ identical output) |
134
+ | `ticks` | `300` | Settlement iterations |
135
+ | `nodeRadius` | degree-derived | Per-node radius: a fixed `number`, `NodeRadiusOptions`, or `(degree, node) => number` |
136
+ | `linkDistance` / `linkStrength` | `60` / `0.08` | Spring target length and stiffness |
137
+ | `chargeStrength` | `-240` | Repulsion magnitude (negative pushes apart) |
138
+ | `collisionPadding` | `2` | Extra spacing beyond `r_i + r_j` |
139
+ | `communityStrength` | `0.05` | Pull toward the node's community centroid |
140
+ | `boundsPadding` | `24` | Keep every node center this many px from each edge |
141
+
142
+ Each positioned node carries a stable render radius `r` (degree-derived by default, overrideable via `nodeRadius`), and the result includes `communities` centroids over the final positions.
143
+
125
144
  All helpers are pure: they never mutate their inputs and (except for an optional snapshot timestamp) produce identical output for identical input.
126
145
 
127
146
  ## What You Get
package/dist/index.d.ts CHANGED
@@ -20,19 +20,31 @@ interface CommunityGraph {
20
20
  }
21
21
  /** Deterministic layout algorithms understood by {@link layoutCommunityGraph}. */
22
22
  type GraphLayoutAlgorithm = 'force' | 'radial' | 'community' | 'manual';
23
- /** A node with computed 2D coordinates, degree, and resolved community. */
23
+ /** A node with computed 2D coordinates, render radius, degree, and community. */
24
24
  interface PositionedGraphNode extends GraphNode {
25
25
  x: number;
26
26
  y: number;
27
+ /** Stable render radius, derived from degree/weight by default. */
28
+ r: number;
27
29
  degree: number;
28
30
  communityId?: string;
29
31
  }
32
+ /** A community with a computed centroid over its positioned member nodes. */
33
+ interface PositionedCommunity {
34
+ id: string;
35
+ x: number;
36
+ y: number;
37
+ /** Number of member nodes that contributed to the centroid. */
38
+ size: number;
39
+ }
30
40
  /** Result of laying out a {@link CommunityGraph} into 2D space. */
31
41
  interface PositionedGraph {
32
42
  nodes: PositionedGraphNode[];
33
43
  edges: GraphEdge[];
34
44
  /** Lookup from node id to its positioned node. */
35
45
  nodeIndex: Map<string, PositionedGraphNode>;
46
+ /** Community centroids over the final positions (empty when none). */
47
+ communities: PositionedCommunity[];
36
48
  }
37
49
  /** Axis-aligned bounding box around a set of positioned nodes. */
38
50
  interface GraphBounds {
@@ -110,24 +122,48 @@ interface GraphFilter {
110
122
  */
111
123
  declare function filterCommunityGraph(graph: CommunityGraph | null | undefined, filter?: GraphFilter): CommunityGraph;
112
124
 
125
+ /**
126
+ * How to derive each node's render radius. Either a fixed pixel value, a set of
127
+ * {@link NodeRadiusOptions} fed to the default degree→radius mapping, or a full
128
+ * resolver `(degree, node) => number`.
129
+ */
130
+ type NodeRadiusResolver = number | NodeRadiusOptions | ((degree: number, node: GraphNode) => number);
113
131
  interface LayoutOptions {
132
+ /** Layout algorithm. `force` runs deterministic settlement; the others are closed-form. */
114
133
  algorithm?: GraphLayoutAlgorithm;
115
134
  width?: number;
116
135
  height?: number;
136
+ /** Seed for the deterministic PRNG used by `force` initial jitter. Identical seed ⇒ identical output. */
137
+ seed?: number;
138
+ /** Number of settlement iterations for the `force` algorithm. */
139
+ ticks?: number;
140
+ /** Per-node render radius (default: degree-derived, clamped 5..16). */
141
+ nodeRadius?: NodeRadiusResolver;
142
+ /** Target edge length in px for the spring force (`force` only). */
143
+ linkDistance?: number;
144
+ /** Spring stiffness 0..1 (`force` only). */
145
+ linkStrength?: number;
146
+ /** Repulsion magnitude; negative pushes nodes apart (`force` only). */
147
+ chargeStrength?: number;
148
+ /** Extra spacing beyond `r_i + r_j` when resolving collisions (`force` only). */
149
+ collisionPadding?: number;
150
+ /** Pull toward the node's community centroid, 0..1 (`force` only). */
151
+ communityStrength?: number;
152
+ /** Keep every node center at least this many px from each canvas edge. */
153
+ boundsPadding?: number;
117
154
  }
118
155
  /**
119
156
  * Deterministically position the nodes of a {@link CommunityGraph} in 2D space.
120
157
  *
121
- * The layout is a closed-form function of (graph, algorithm, width, height) with
122
- * no randomness or iteration, so the same input always yields the same
123
- * coordinates safe for snapshot rendering and server-side generation. Each
124
- * node is also annotated with its degree and resolved community.
158
+ * For the default `force` algorithm this runs a fixed-iteration, seeded force
159
+ * settlement (spring link attraction, charge repulsion, collision spacing,
160
+ * community cohesion, centering, and bounds clamping). It is fully synchronous
161
+ * and headless no animation frames so the same `(graph, options)` always
162
+ * yields identical coordinates, safe for snapshot rendering and SSR. The
163
+ * `radial`, `community`, and `manual` algorithms remain closed-form.
125
164
  *
126
- * Algorithms:
127
- * - `force` radial ring with a mild degree-based jitter (default)
128
- * - `radial` plain radial ring at the full layout radius
129
- * - `community` nodes orbit their community's anchor on the ring
130
- * - `manual` same ring as `radial`; intended as a base for host-driven pinning
165
+ * Every node is annotated with its render radius `r`, degree, and resolved
166
+ * community; the result also carries community centroids over the final layout.
131
167
  */
132
168
  declare function layoutCommunityGraph(graph: CommunityGraph, options?: LayoutOptions): PositionedGraph;
133
169
 
@@ -299,6 +335,6 @@ declare class GraphController {
299
335
  saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>;
300
336
  }
301
337
 
302
- declare const VERSION = "2026.6.8";
338
+ declare const VERSION = "2026.7.0";
303
339
 
304
- export { COMMUNITY_COLORS, type CommunityGraph, type ExpandOptions, type FitOptions, GRAPH_SNAPSHOT_VERSION, type GraphBounds, type GraphCommunity, GraphController, type GraphControllerDb, type GraphControllerListener, type GraphControllerOptions, type GraphControllerState, type GraphControllerStatus, type GraphEdge, type GraphFilter, type GraphLayoutAlgorithm, type GraphLayoutState, type GraphNode, type GraphSnapshot, type GraphSourceMode, type GraphSourceRef, type GraphTransitionState, type LayoutOptions, type NodeRadiusOptions, type PositionedGraph, type PositionedGraphNode, type SerializeSnapshotOptions, UNASSIGNED_COMMUNITY_COLOR, VERSION, type ViewportTransform, buildAdjacency, colorForCommunity, computeDegrees, computeGraphBounds, deserializeGraphSnapshot, expandNeighborhood, filterCommunityGraph, fitGraphToViewport, layoutCommunityGraph, neighborhoodSubgraph, neighborsOf, nodeRadius, serializeGraphSnapshot, stringifyGraphSnapshot, subgraphForNodes };
340
+ export { COMMUNITY_COLORS, type CommunityGraph, type ExpandOptions, type FitOptions, GRAPH_SNAPSHOT_VERSION, type GraphBounds, type GraphCommunity, GraphController, type GraphControllerDb, type GraphControllerListener, type GraphControllerOptions, type GraphControllerState, type GraphControllerStatus, type GraphEdge, type GraphFilter, type GraphLayoutAlgorithm, type GraphLayoutState, type GraphNode, type GraphSnapshot, type GraphSourceMode, type GraphSourceRef, type GraphTransitionState, type LayoutOptions, type NodeRadiusOptions, type NodeRadiusResolver, type PositionedCommunity, type PositionedGraph, type PositionedGraphNode, type SerializeSnapshotOptions, UNASSIGNED_COMMUNITY_COLOR, VERSION, type ViewportTransform, buildAdjacency, colorForCommunity, computeDegrees, computeGraphBounds, deserializeGraphSnapshot, expandNeighborhood, filterCommunityGraph, fitGraphToViewport, layoutCommunityGraph, neighborhoodSubgraph, neighborsOf, nodeRadius, serializeGraphSnapshot, stringifyGraphSnapshot, subgraphForNodes };
package/dist/index.js CHANGED
@@ -64,13 +64,47 @@ function filterCommunityGraph(graph, filter) {
64
64
  return { nodes, edges, communities };
65
65
  }
66
66
 
67
+ // src/rng.ts
68
+ function mulberry32(seed) {
69
+ let a = seed >>> 0 || 2654435769;
70
+ return function next() {
71
+ a |= 0;
72
+ a = a + 1831565813 | 0;
73
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
74
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
75
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
76
+ };
77
+ }
78
+
67
79
  // src/layout.ts
68
- var DEFAULT_WIDTH = 760;
69
- var DEFAULT_HEIGHT = 460;
80
+ var DEFAULTS = {
81
+ width: 760,
82
+ height: 460,
83
+ seed: 1,
84
+ ticks: 300,
85
+ linkDistance: 60,
86
+ linkStrength: 0.08,
87
+ chargeStrength: -240,
88
+ collisionPadding: 2,
89
+ communityStrength: 0.05,
90
+ boundsPadding: 24
91
+ };
92
+ var GRAVITY = 0.02;
93
+ function resolveRadius(resolver, degree, node) {
94
+ if (resolver === void 0) return nodeRadius(degree);
95
+ if (typeof resolver === "number") return resolver;
96
+ if (typeof resolver === "function") return resolver(degree, node);
97
+ return nodeRadius(degree, resolver);
98
+ }
99
+ function clampToRange(value, lo, hi) {
100
+ if (lo > hi) return (lo + hi) / 2;
101
+ return Math.max(lo, Math.min(hi, value));
102
+ }
70
103
  function layoutCommunityGraph(graph, options = {}) {
71
104
  const algorithm = options.algorithm ?? "force";
72
- const width = options.width ?? DEFAULT_WIDTH;
73
- const height = options.height ?? DEFAULT_HEIGHT;
105
+ const width = options.width ?? DEFAULTS.width;
106
+ const height = options.height ?? DEFAULTS.height;
107
+ const boundsPadding = options.boundsPadding ?? DEFAULTS.boundsPadding;
74
108
  const degree = computeDegrees(graph);
75
109
  const communityByNode = /* @__PURE__ */ new Map();
76
110
  for (const community of graph.communities) {
@@ -81,29 +115,172 @@ function layoutCommunityGraph(graph, options = {}) {
81
115
  const centerX = width / 2;
82
116
  const centerY = height / 2;
83
117
  const radius = Math.max(40, Math.min(width, height) * 0.38);
84
- const nodes = graph.nodes.map((node, index) => {
85
- const angle = Math.PI * 2 * index / Math.max(1, graph.nodes.length);
86
- const communityIndex = graph.communities.findIndex(
87
- (community) => community.id === communityByNode.get(node.id)
88
- );
118
+ const n = graph.nodes.length;
119
+ const r = graph.nodes.map(
120
+ (node) => resolveRadius(options.nodeRadius, degree.get(node.id) ?? 0, node)
121
+ );
122
+ const indexOf = /* @__PURE__ */ new Map();
123
+ graph.nodes.forEach((node, i) => indexOf.set(node.id, i));
124
+ const communityIndexById = /* @__PURE__ */ new Map();
125
+ graph.communities.forEach((community, i) => communityIndexById.set(community.id, i));
126
+ const x = new Float64Array(n);
127
+ const y = new Float64Array(n);
128
+ graph.nodes.forEach((node, index) => {
129
+ const angle = Math.PI * 2 * index / Math.max(1, n);
130
+ const communityId = communityByNode.get(node.id);
131
+ const communityIndex = communityId !== void 0 ? communityIndexById.get(communityId) ?? -1 : -1;
89
132
  const communityAngle = Math.PI * 2 * Math.max(0, communityIndex) / Math.max(1, graph.communities.length);
133
+ const useCommunityOrbit = algorithm === "community" || algorithm === "force";
90
134
  const communityRadius = algorithm === "community" ? radius * 0.55 : radius;
91
135
  const localRadius = algorithm === "force" ? radius * (0.7 + (degree.get(node.id) ?? 0) % 4 * 0.08) : radius;
92
- const x = algorithm === "community" ? centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46 : centerX + Math.cos(angle) * localRadius;
93
- const y = algorithm === "community" ? centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46 : centerY + Math.sin(angle) * (algorithm === "radial" ? radius : localRadius * 0.72);
94
- return {
95
- ...node,
96
- x,
97
- y,
98
- degree: degree.get(node.id) ?? 0,
99
- communityId: communityByNode.get(node.id)
100
- };
136
+ if (useCommunityOrbit && communityIndex >= 0) {
137
+ x[index] = centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46;
138
+ y[index] = centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46;
139
+ } else {
140
+ x[index] = centerX + Math.cos(angle) * localRadius;
141
+ y[index] = centerY + Math.sin(angle) * (algorithm === "radial" ? radius : localRadius * 0.72);
142
+ }
101
143
  });
102
- return {
103
- nodes,
104
- edges: graph.edges,
105
- nodeIndex: new Map(nodes.map((node) => [node.id, node]))
106
- };
144
+ if (algorithm === "force" && n > 1) {
145
+ const ticks = Math.max(0, Math.floor(options.ticks ?? DEFAULTS.ticks));
146
+ const seed = options.seed ?? DEFAULTS.seed;
147
+ const linkDistance = options.linkDistance ?? DEFAULTS.linkDistance;
148
+ const linkStrength = options.linkStrength ?? DEFAULTS.linkStrength;
149
+ const chargeStrength = options.chargeStrength ?? DEFAULTS.chargeStrength;
150
+ const collisionPadding = options.collisionPadding ?? DEFAULTS.collisionPadding;
151
+ const communityStrength = options.communityStrength ?? DEFAULTS.communityStrength;
152
+ const rng = mulberry32(seed);
153
+ for (let i = 0; i < n; i++) {
154
+ x[i] += (rng() - 0.5) * 8;
155
+ y[i] += (rng() - 0.5) * 8;
156
+ }
157
+ const links = [];
158
+ for (const edge of graph.edges) {
159
+ const a = indexOf.get(edge.source);
160
+ const b = indexOf.get(edge.target);
161
+ if (a === void 0 || b === void 0 || a === b) continue;
162
+ links.push({ a, b, weight: edge.weight > 0 ? edge.weight : 1 });
163
+ }
164
+ const communityOf = graph.nodes.map((node) => communityByNode.get(node.id));
165
+ const communityIds = graph.communities.map((c) => c.id);
166
+ const dispX = new Float64Array(n);
167
+ const dispY = new Float64Array(n);
168
+ const chargeCutoff = linkDistance * 6;
169
+ const cooling = ticks > 0 ? Math.pow(1e-3, 1 / ticks) : 1;
170
+ let alpha = 1;
171
+ for (let t = 0; t < ticks; t++) {
172
+ dispX.fill(0);
173
+ dispY.fill(0);
174
+ for (const link of links) {
175
+ let dx = x[link.b] - x[link.a];
176
+ let dy = y[link.b] - y[link.a];
177
+ const dist = Math.max(0.01, Math.hypot(dx, dy));
178
+ const force = (dist - linkDistance) / dist * linkStrength * link.weight;
179
+ dx *= force;
180
+ dy *= force;
181
+ dispX[link.a] += dx;
182
+ dispY[link.a] += dy;
183
+ dispX[link.b] -= dx;
184
+ dispY[link.b] -= dy;
185
+ }
186
+ for (let i = 0; i < n; i++) {
187
+ for (let j = i + 1; j < n; j++) {
188
+ const dx = x[i] - x[j];
189
+ const dy = y[i] - y[j];
190
+ const dist = Math.max(0.01, Math.hypot(dx, dy));
191
+ if (dist > chargeCutoff) continue;
192
+ const mag = -chargeStrength / dist;
193
+ const ux = dx / dist * mag;
194
+ const uy = dy / dist * mag;
195
+ dispX[i] += ux;
196
+ dispY[i] += uy;
197
+ dispX[j] -= ux;
198
+ dispY[j] -= uy;
199
+ }
200
+ }
201
+ if (communityStrength > 0 && communityIds.length > 0) {
202
+ const cx = new Float64Array(communityIds.length);
203
+ const cy = new Float64Array(communityIds.length);
204
+ const cn = new Int32Array(communityIds.length);
205
+ const idxById = /* @__PURE__ */ new Map();
206
+ communityIds.forEach((id, i) => idxById.set(id, i));
207
+ for (let i = 0; i < n; i++) {
208
+ const cid = communityOf[i];
209
+ if (cid === void 0) continue;
210
+ const ci = idxById.get(cid);
211
+ if (ci === void 0) continue;
212
+ cx[ci] += x[i];
213
+ cy[ci] += y[i];
214
+ cn[ci] += 1;
215
+ }
216
+ for (let i = 0; i < n; i++) {
217
+ const cid = communityOf[i];
218
+ if (cid === void 0) continue;
219
+ const ci = idxById.get(cid);
220
+ if (ci === void 0 || cn[ci] === 0) continue;
221
+ dispX[i] += (cx[ci] / cn[ci] - x[i]) * communityStrength;
222
+ dispY[i] += (cy[ci] / cn[ci] - y[i]) * communityStrength;
223
+ }
224
+ }
225
+ for (let i = 0; i < n; i++) {
226
+ dispX[i] += (centerX - x[i]) * GRAVITY;
227
+ dispY[i] += (centerY - y[i]) * GRAVITY;
228
+ }
229
+ for (let i = 0; i < n; i++) {
230
+ x[i] += dispX[i] * alpha;
231
+ y[i] += dispY[i] * alpha;
232
+ }
233
+ for (let i = 0; i < n; i++) {
234
+ for (let j = i + 1; j < n; j++) {
235
+ const dx = x[i] - x[j];
236
+ const dy = y[i] - y[j];
237
+ const dist = Math.max(0.01, Math.hypot(dx, dy));
238
+ const minDist = r[i] + r[j] + collisionPadding;
239
+ if (dist >= minDist) continue;
240
+ const push = (minDist - dist) / 2;
241
+ const ux = dx / dist * push;
242
+ const uy = dy / dist * push;
243
+ x[i] += ux;
244
+ y[i] += uy;
245
+ x[j] -= ux;
246
+ y[j] -= uy;
247
+ }
248
+ }
249
+ for (let i = 0; i < n; i++) {
250
+ x[i] = clampToRange(x[i], boundsPadding + r[i], width - boundsPadding - r[i]);
251
+ y[i] = clampToRange(y[i], boundsPadding + r[i], height - boundsPadding - r[i]);
252
+ }
253
+ alpha *= cooling;
254
+ }
255
+ } else {
256
+ for (let i = 0; i < n; i++) {
257
+ x[i] = clampToRange(x[i], boundsPadding + r[i], width - boundsPadding - r[i]);
258
+ y[i] = clampToRange(y[i], boundsPadding + r[i], height - boundsPadding - r[i]);
259
+ }
260
+ }
261
+ const nodes = graph.nodes.map((node, index) => ({
262
+ ...node,
263
+ x: x[index],
264
+ y: y[index],
265
+ r: r[index],
266
+ degree: degree.get(node.id) ?? 0,
267
+ communityId: communityByNode.get(node.id)
268
+ }));
269
+ const nodeIndex = new Map(nodes.map((node) => [node.id, node]));
270
+ const communities = graph.communities.map((community) => {
271
+ let sx = 0;
272
+ let sy = 0;
273
+ let size = 0;
274
+ for (const nodeId of community.nodes) {
275
+ const positioned = nodeIndex.get(nodeId);
276
+ if (!positioned) continue;
277
+ sx += positioned.x;
278
+ sy += positioned.y;
279
+ size += 1;
280
+ }
281
+ return size > 0 ? { id: community.id, x: sx / size, y: sy / size, size } : { id: community.id, x: centerX, y: centerY, size: 0 };
282
+ });
283
+ return { nodes, edges: graph.edges, nodeIndex, communities };
107
284
  }
108
285
 
109
286
  // src/bounds.ts
@@ -432,7 +609,7 @@ var GraphController = class _GraphController {
432
609
  };
433
610
 
434
611
  // src/index.ts
435
- var VERSION = "2026.6.8";
612
+ var VERSION = "2026.7.0";
436
613
 
437
614
  export { COMMUNITY_COLORS, GRAPH_SNAPSHOT_VERSION, GraphController, UNASSIGNED_COMMUNITY_COLOR, VERSION, buildAdjacency, colorForCommunity, computeDegrees, computeGraphBounds, deserializeGraphSnapshot, expandNeighborhood, filterCommunityGraph, fitGraphToViewport, layoutCommunityGraph, neighborhoodSubgraph, neighborsOf, nodeRadius, serializeGraphSnapshot, stringifyGraphSnapshot, subgraphForNodes };
438
615
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/degree.ts","../src/color.ts","../src/filter.ts","../src/layout.ts","../src/bounds.ts","../src/neighborhood.ts","../src/serialize.ts","../src/controller.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { CommunityGraph } from './types.js'\n\n/** Count the (undirected) degree of every node, including isolated nodes (0). */\nexport function computeDegrees(graph: CommunityGraph): Map<string, number> {\n const degree = new Map<string, number>()\n for (const node of graph.nodes) degree.set(node.id, 0)\n for (const edge of graph.edges) {\n degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1)\n degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1)\n }\n return degree\n}\n\nexport interface NodeRadiusOptions {\n /** Radius of a degree-0 node. */\n base?: number\n /** Pixels added per unit of degree. */\n perDegree?: number\n /** Hard floor on the radius. */\n min?: number\n /** Hard ceiling on the radius. */\n max?: number\n}\n\n/**\n * Map a node degree to a render radius. Defaults match the values historically\n * used by the React `GraphView`: `clamp(5 + degree * 1.5, 5, 16)`.\n */\nexport function nodeRadius(degree: number, options: NodeRadiusOptions = {}): number {\n const base = options.base ?? 5\n const perDegree = options.perDegree ?? 1.5\n const min = options.min ?? 5\n const max = options.max ?? 16\n return Math.max(min, Math.min(max, base + degree * perDegree))\n}\n","/** Default qualitative palette for community coloring (8 hues). */\nexport const COMMUNITY_COLORS = [\n '#2f6fbb',\n '#d97706',\n '#218838',\n '#7c3aed',\n '#c2410c',\n '#0f766e',\n '#be185d',\n '#4b5563',\n] as const\n\n/** Color used for nodes that are not assigned to any community. */\nexport const UNASSIGNED_COMMUNITY_COLOR = '#64748b'\n\n/**\n * Deterministically assign a color to a community id by hashing the id into the\n * palette. The same id always maps to the same color across runs and hosts.\n * Pass a custom `palette` to theme the output. Matches the historical\n * `GraphView` color assignment.\n */\nexport function colorForCommunity(\n communityId: string | undefined,\n palette: readonly string[] = COMMUNITY_COLORS,\n unassignedColor: string = UNASSIGNED_COMMUNITY_COLOR,\n): string {\n if (!communityId) return unassignedColor\n if (palette.length === 0) return unassignedColor\n let hash = 0\n for (const char of communityId) hash = (hash * 31 + char.charCodeAt(0)) | 0\n return palette[Math.abs(hash) % palette.length]\n}\n","import type { CommunityGraph, GraphNode } from './types.js'\n\nexport interface GraphFilter {\n /** Keep only nodes belonging to these communities (by community id). */\n communityIds?: string[]\n /** Keep only edges whose `kind` is in this set. */\n edgeKinds?: string[]\n /** Explicit allow-list of node ids. Useful for privacy / visibility filtering. */\n nodeIds?: string[]\n /**\n * Arbitrary per-node predicate evaluated against the (allow-listed) nodes.\n * Returning `false` removes the node and any edge touching it. Hosts whose\n * nodes carry extra metadata (privacy class, type, etc.) can filter on it here.\n */\n nodePredicate?: (node: GraphNode) => boolean\n}\n\n/**\n * Produce a new {@link CommunityGraph} containing only the nodes, edges, and\n * community memberships permitted by `filter`. Pure — the input is not mutated.\n *\n * Filtering precedence:\n * 1. `communityIds` (if non-empty) restricts the candidate node set to members\n * of those communities; otherwise `nodeIds` (if given) is the candidate set,\n * else all nodes.\n * 2. `nodePredicate` further narrows the candidate set.\n * 3. Edges survive only when both endpoints survive and `edgeKinds` permits.\n * 4. Empty communities are dropped.\n */\nexport function filterCommunityGraph(\n graph: CommunityGraph | null | undefined,\n filter?: GraphFilter,\n): CommunityGraph {\n if (!graph) return { nodes: [], edges: [], communities: [] }\n\n const nodeIds = new Set(filter?.nodeIds ?? graph.nodes.map((node) => node.id))\n if (filter?.communityIds?.length) {\n const allowedCommunities = new Set(filter.communityIds)\n nodeIds.clear()\n for (const community of graph.communities) {\n if (allowedCommunities.has(community.id)) {\n for (const nodeId of community.nodes) nodeIds.add(nodeId)\n }\n }\n }\n\n const predicate = filter?.nodePredicate\n if (predicate) {\n for (const node of graph.nodes) {\n if (nodeIds.has(node.id) && !predicate(node)) nodeIds.delete(node.id)\n }\n }\n\n const edgeKinds = filter?.edgeKinds ? new Set(filter.edgeKinds) : null\n const nodes = graph.nodes.filter((node) => nodeIds.has(node.id))\n const edges = graph.edges.filter((edge) => (\n nodeIds.has(edge.source)\n && nodeIds.has(edge.target)\n && (!edgeKinds || edgeKinds.has(edge.kind ?? ''))\n ))\n const communities = graph.communities\n .map((community) => ({ ...community, nodes: community.nodes.filter((nodeId) => nodeIds.has(nodeId)) }))\n .filter((community) => community.nodes.length > 0)\n return { nodes, edges, communities }\n}\n","import { computeDegrees } from './degree.js'\nimport type {\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraph,\n PositionedGraphNode,\n} from './types.js'\n\nexport interface LayoutOptions {\n algorithm?: GraphLayoutAlgorithm\n width?: number\n height?: number\n}\n\nconst DEFAULT_WIDTH = 760\nconst DEFAULT_HEIGHT = 460\n\n/**\n * Deterministically position the nodes of a {@link CommunityGraph} in 2D space.\n *\n * The layout is a closed-form function of (graph, algorithm, width, height) with\n * no randomness or iteration, so the same input always yields the same\n * coordinates — safe for snapshot rendering and server-side generation. Each\n * node is also annotated with its degree and resolved community.\n *\n * Algorithms:\n * - `force` radial ring with a mild degree-based jitter (default)\n * - `radial` plain radial ring at the full layout radius\n * - `community` nodes orbit their community's anchor on the ring\n * - `manual` same ring as `radial`; intended as a base for host-driven pinning\n */\nexport function layoutCommunityGraph(\n graph: CommunityGraph,\n options: LayoutOptions = {},\n): PositionedGraph {\n const algorithm = options.algorithm ?? 'force'\n const width = options.width ?? DEFAULT_WIDTH\n const height = options.height ?? DEFAULT_HEIGHT\n\n const degree = computeDegrees(graph)\n\n const communityByNode = new Map<string, string>()\n for (const community of graph.communities) {\n for (const nodeId of community.nodes) {\n if (!communityByNode.has(nodeId)) communityByNode.set(nodeId, community.id)\n }\n }\n\n const centerX = width / 2\n const centerY = height / 2\n const radius = Math.max(40, Math.min(width, height) * 0.38)\n\n const nodes = graph.nodes.map((node, index): PositionedGraphNode => {\n const angle = (Math.PI * 2 * index) / Math.max(1, graph.nodes.length)\n const communityIndex = graph.communities.findIndex(\n (community) => community.id === communityByNode.get(node.id),\n )\n const communityAngle = (Math.PI * 2 * Math.max(0, communityIndex)) / Math.max(1, graph.communities.length)\n const communityRadius = algorithm === 'community' ? radius * 0.55 : radius\n const localRadius = algorithm === 'force'\n ? radius * (0.7 + ((degree.get(node.id) ?? 0) % 4) * 0.08)\n : radius\n const x = algorithm === 'community'\n ? centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46\n : centerX + Math.cos(angle) * localRadius\n const y = algorithm === 'community'\n ? centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46\n : centerY + Math.sin(angle) * (algorithm === 'radial' ? radius : localRadius * 0.72)\n return {\n ...node,\n x,\n y,\n degree: degree.get(node.id) ?? 0,\n communityId: communityByNode.get(node.id),\n }\n })\n\n return {\n nodes,\n edges: graph.edges,\n nodeIndex: new Map(nodes.map((node) => [node.id, node])),\n }\n}\n","import type { GraphBounds, PositionedGraphNode, ViewportTransform } from './types.js'\n\nconst EMPTY_BOUNDS: GraphBounds = {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n centerX: 0,\n centerY: 0,\n}\n\n/**\n * Compute the axis-aligned bounding box around a set of positioned nodes.\n * Returns an all-zero box for an empty input.\n */\nexport function computeGraphBounds(\n nodes: ReadonlyArray<Pick<PositionedGraphNode, 'x' | 'y'>>,\n): GraphBounds {\n if (nodes.length === 0) return { ...EMPTY_BOUNDS }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const node of nodes) {\n if (node.x < minX) minX = node.x\n if (node.y < minY) minY = node.y\n if (node.x > maxX) maxX = node.x\n if (node.y > maxY) maxY = node.y\n }\n return {\n minX,\n minY,\n maxX,\n maxY,\n width: maxX - minX,\n height: maxY - minY,\n centerX: (minX + maxX) / 2,\n centerY: (minY + maxY) / 2,\n }\n}\n\nexport interface FitOptions {\n /** Uniform padding (in viewport units) to leave around the graph. */\n padding?: number\n /** Clamp the computed scale to this minimum. */\n minScale?: number\n /** Clamp the computed scale to this maximum. */\n maxScale?: number\n}\n\n/**\n * Compute a {@link ViewportTransform} (uniform scale + translation) that fits\n * `bounds` centered within a `viewport` of the given width/height. Apply the\n * result as `translate(offsetX, offsetY) scale(scale)` in SVG/canvas space.\n */\nexport function fitGraphToViewport(\n bounds: GraphBounds,\n viewport: { width: number; height: number },\n options: FitOptions = {},\n): ViewportTransform {\n const padding = options.padding ?? 0\n const minScale = options.minScale ?? 0\n const maxScale = options.maxScale ?? Infinity\n\n const availableWidth = Math.max(0, viewport.width - padding * 2)\n const availableHeight = Math.max(0, viewport.height - padding * 2)\n\n let scale = 1\n if (bounds.width > 0 || bounds.height > 0) {\n const scaleX = bounds.width > 0 ? availableWidth / bounds.width : Infinity\n const scaleY = bounds.height > 0 ? availableHeight / bounds.height : Infinity\n scale = Math.min(scaleX, scaleY)\n }\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\n scale = Math.max(minScale, Math.min(maxScale, scale))\n\n const offsetX = viewport.width / 2 - bounds.centerX * scale\n const offsetY = viewport.height / 2 - bounds.centerY * scale\n return { scale, offsetX, offsetY }\n}\n","import { filterCommunityGraph } from './filter.js'\nimport type { CommunityGraph } from './types.js'\n\n/** Build an undirected adjacency map from a graph's edges. */\nexport function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>> {\n const adjacency = new Map<string, Set<string>>()\n const ensure = (id: string) => {\n let set = adjacency.get(id)\n if (!set) {\n set = new Set<string>()\n adjacency.set(id, set)\n }\n return set\n }\n for (const node of graph.nodes) ensure(node.id)\n for (const edge of graph.edges) {\n ensure(edge.source).add(edge.target)\n ensure(edge.target).add(edge.source)\n }\n return adjacency\n}\n\n/** Return the set of node ids directly adjacent to `nodeId` (excludes itself). */\nexport function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string> {\n return new Set(buildAdjacency(graph).get(nodeId) ?? [])\n}\n\nexport interface ExpandOptions {\n /** How many edge-hops to traverse from the seeds. Default 1. */\n depth?: number\n /** Include the seed ids in the result. Default true. */\n includeSeeds?: boolean\n}\n\n/**\n * Breadth-first expansion of one or more seed nodes out to `depth` hops.\n * Returns the set of reached node ids (seeds included by default).\n */\nexport function expandNeighborhood(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): Set<string> {\n const depth = options.depth ?? 1\n const includeSeeds = options.includeSeeds ?? true\n const adjacency = buildAdjacency(graph)\n\n const seen = new Set<string>()\n let frontier = new Set<string>()\n for (const seed of seeds) {\n if (adjacency.has(seed) || depth === 0) {\n seen.add(seed)\n frontier.add(seed)\n }\n }\n\n for (let hop = 0; hop < depth; hop++) {\n const next = new Set<string>()\n for (const id of frontier) {\n for (const neighbor of adjacency.get(id) ?? []) {\n if (!seen.has(neighbor)) {\n seen.add(neighbor)\n next.add(neighbor)\n }\n }\n }\n if (next.size === 0) break\n frontier = next\n }\n\n if (!includeSeeds) {\n for (const seed of seeds) seen.delete(seed)\n }\n return seen\n}\n\n/**\n * Extract the induced subgraph over `nodeIds` — a {@link CommunityGraph}\n * containing only those nodes, the edges between them, and the trimmed\n * community memberships. Thin wrapper over {@link filterCommunityGraph}.\n */\nexport function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph {\n return filterCommunityGraph(graph, { nodeIds: Array.from(nodeIds) })\n}\n\n/**\n * Convenience: the induced subgraph of a seed node plus its `depth`-hop\n * neighborhood. Useful for \"expand selection\" interactions.\n */\nexport function neighborhoodSubgraph(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): CommunityGraph {\n return subgraphForNodes(graph, expandNeighborhood(graph, seeds, options))\n}\n","import type { CommunityGraph, GraphLayoutAlgorithm } from './types.js'\n\n/** Current on-disk version for {@link GraphSnapshot}. */\nexport const GRAPH_SNAPSHOT_VERSION = 1\n\n/**\n * A self-contained, serializable representation of a community graph plus the\n * layout intent used to render it. Designed to be written to a static JSON file\n * and consumed by a JS-only host (e.g. a documentation site) without recomputing\n * the graph.\n */\nexport interface GraphSnapshot {\n version: number\n graph: CommunityGraph\n layout?: {\n algorithm: GraphLayoutAlgorithm\n width: number\n height: number\n }\n generatedAt?: string\n}\n\nexport interface SerializeSnapshotOptions {\n layout?: GraphSnapshot['layout']\n /** ISO timestamp to stamp into the snapshot. Omit for reproducible output. */\n generatedAt?: string\n}\n\nfunction sortedGraph(graph: CommunityGraph): CommunityGraph {\n return {\n nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)),\n edges: [...graph.edges].sort((a, b) => (\n a.source.localeCompare(b.source)\n || a.target.localeCompare(b.target)\n || (a.kind ?? '').localeCompare(b.kind ?? '')\n )),\n communities: [...graph.communities]\n .map((community) => ({ ...community, nodes: [...community.nodes].sort() }))\n .sort((a, b) => a.id.localeCompare(b.id)),\n }\n}\n\n/**\n * Build a deterministic {@link GraphSnapshot} from a graph. Nodes, edges, and\n * community members are sorted so that equal graphs serialize identically\n * (stable diffs, content-addressable caching).\n */\nexport function serializeGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): GraphSnapshot {\n const snapshot: GraphSnapshot = {\n version: GRAPH_SNAPSHOT_VERSION,\n graph: sortedGraph(graph),\n }\n if (options.layout) snapshot.layout = options.layout\n if (options.generatedAt) snapshot.generatedAt = options.generatedAt\n return snapshot\n}\n\n/** Stable JSON string for a graph snapshot. */\nexport function stringifyGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): string {\n return JSON.stringify(serializeGraphSnapshot(graph, options))\n}\n\n/**\n * Validate and unwrap a {@link GraphSnapshot} (or its JSON string) back into a\n * {@link CommunityGraph}. Throws on shape/version mismatch.\n */\nexport function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph {\n const snapshot: unknown = typeof input === 'string' ? JSON.parse(input) : input\n if (!snapshot || typeof snapshot !== 'object') {\n throw new Error('invalid graph snapshot: not an object')\n }\n const candidate = snapshot as Partial<GraphSnapshot>\n if (candidate.version !== GRAPH_SNAPSHOT_VERSION) {\n throw new Error(`unsupported graph snapshot version: ${String(candidate.version)}`)\n }\n const graph = candidate.graph\n if (\n !graph\n || !Array.isArray(graph.nodes)\n || !Array.isArray(graph.edges)\n || !Array.isArray(graph.communities)\n ) {\n throw new Error('invalid graph snapshot: missing graph nodes/edges/communities')\n }\n return graph\n}\n","// Framework-agnostic graph-source controller.\n//\n// Owns the graph \"source mode\" state machine (citations | topics | precomputed\n// | dynamic-search | user-authored), transition tracking, and the mode→fetch\n// dispatch. The capability previously lived inside the React `useGraphController`\n// hook; it now lives here so JS-only hosts can drive the same logic without\n// React. The React hook is a thin `useSyncExternalStore` adapter over this class.\n//\n// This module depends on @fortemi/core for the repositories and shared types.\n// @fortemi/core never depends on @fortemi/graph — the dependency direction is\n// the linear chain: pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react.\n\nimport {\n CommunitiesRepository,\n GraphRepository,\n type CommunityCreateInput,\n type CommunityFilterDefinition,\n type CommunityGraph,\n type CommunitySourceDescriptor,\n type DatabaseClient,\n type EmbeddingSetSelector,\n type QueryExecutor,\n type SimilarityGraphResult,\n} from '@fortemi/core'\nimport type { GraphLayoutAlgorithm } from './types.js'\n\n/** A database handle accepted by the underlying repositories. */\nexport type GraphControllerDb = QueryExecutor & DatabaseClient\n\nexport type GraphSourceMode =\n | 'citations'\n | 'topics'\n | 'precomputed'\n | 'dynamic-search'\n | 'user-authored'\n\nexport interface GraphLayoutState {\n algorithm: GraphLayoutAlgorithm\n pinSelectedNodes?: boolean\n preserveViewport?: boolean\n communitySpacing?: number\n}\n\nexport interface GraphTransitionState {\n fromMode?: GraphSourceMode\n toMode: GraphSourceMode\n reason: 'mode-change' | 'embedding-set-change' | 'community-source-change' | 'filter-change' | 'recompute'\n startedAt: string\n}\n\nexport interface GraphControllerStatus {\n loading: boolean\n error: Error | null\n freshness: 'fresh' | 'stale' | 'unknown' | null\n cache: SimilarityGraphResult['cache'] | null\n}\n\nexport type GraphSourceRef = SimilarityGraphResult['graphSource'] | { id: string; name: string }\n\n/** The full, framework-agnostic controller state surfaced to subscribers. */\nexport interface GraphControllerState {\n mode: GraphSourceMode\n graph: CommunityGraph | null\n graphSource?: GraphSourceRef\n communitySource?: CommunitySourceDescriptor\n embeddingSetSelector?: EmbeddingSetSelector\n filters?: CommunityFilterDefinition\n layout: GraphLayoutState\n status: GraphControllerStatus\n transition?: GraphTransitionState\n}\n\nexport interface GraphControllerOptions {\n initialMode?: GraphSourceMode\n initialEmbeddingSetSelector?: EmbeddingSetSelector\n initialCommunitySourceId?: string\n initialFilters?: CommunityFilterDefinition\n layout?: Partial<GraphLayoutState>\n}\n\nconst DEFAULT_LAYOUT: GraphLayoutState = {\n algorithm: 'force',\n preserveViewport: true,\n communitySpacing: 1,\n}\n\nexport type GraphControllerListener = (state: GraphControllerState) => void\n\n/**\n * Drives graph-source selection and loading independent of any UI framework.\n *\n * Construct with pre-built repositories (handy for tests) or via\n * {@link GraphController.fromDb}. Subscribe with {@link subscribe} and read the\n * current state with {@link getState}; call {@link start} once to trigger the\n * initial load. Setters update state and schedule a refresh, mirroring the\n * effect-driven behavior of the original React hook.\n */\nexport class GraphController {\n private readonly graphRepo: GraphRepository\n private readonly communityRepo: CommunitiesRepository\n private readonly listeners = new Set<GraphControllerListener>()\n private communitySourceId: string | null\n private state: GraphControllerState\n\n /** Build a controller from a database handle (production path). */\n static fromDb(db: GraphControllerDb, options: GraphControllerOptions = {}): GraphController {\n return new GraphController(new GraphRepository(db), new CommunitiesRepository(db), options)\n }\n\n constructor(\n graphRepo: GraphRepository,\n communityRepo: CommunitiesRepository,\n options: GraphControllerOptions = {},\n ) {\n this.graphRepo = graphRepo\n this.communityRepo = communityRepo\n this.communitySourceId = options.initialCommunitySourceId ?? null\n this.state = {\n mode: options.initialMode ?? 'citations',\n graph: null,\n graphSource: undefined,\n communitySource: undefined,\n embeddingSetSelector: options.initialEmbeddingSetSelector,\n filters: options.initialFilters,\n layout: { ...DEFAULT_LAYOUT, ...options.layout },\n status: { loading: false, error: null, freshness: null, cache: null },\n transition: undefined,\n }\n }\n\n getState(): GraphControllerState {\n return this.state\n }\n\n subscribe(listener: GraphControllerListener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /** Run the initial load. Call once after construction (the hook calls this on mount). */\n async start(): Promise<void> {\n await this.refresh()\n }\n\n private setState(patch: Partial<GraphControllerState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener(this.state)\n }\n\n private beginTransition(\n toMode: GraphSourceMode,\n reason: GraphTransitionState['reason'],\n fromMode: GraphSourceMode = this.state.mode,\n ): void {\n this.setState({ transition: { fromMode, toMode, reason, startedAt: new Date().toISOString() } })\n }\n\n async refresh(): Promise<void> {\n try {\n this.setState({ status: { ...this.state.status, loading: true, error: null } })\n const sources = await this.communityRepo.listCommunitySources()\n const activeCommunity = this.communitySourceId\n ? sources.find((source) => source.id === this.communitySourceId)\n : undefined\n this.setState({ communitySource: activeCommunity })\n\n const mode = this.state.mode\n\n if (mode === 'citations') {\n const nextGraph = await this.graphRepo.buildLinkGraph()\n this.setState({\n graph: nextGraph,\n graphSource: { id: 'citations', name: 'Citation graph' },\n status: { loading: false, error: null, freshness: 'fresh', cache: null },\n })\n return\n }\n\n if (mode === 'topics') {\n const selector = this.state.embeddingSetSelector\n if (!selector) throw new Error('topics mode requires an embedding-set selector')\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({ selector })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n\n if (mode === 'precomputed') {\n if (!activeCommunity?.graphSourceId) {\n throw new Error('precomputed mode requires a community source with graphSourceId')\n }\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n const nextGraph = await this.graphRepo.loadGraphArtifact(\n activeCommunity.graphSourceId,\n assignments.map((assignment) => assignment.noteId),\n )\n this.setState({\n graph: nextGraph,\n graphSource: { id: activeCommunity.graphSourceId, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'dynamic-search') {\n const assignments = await this.communityRepo.previewDynamicCommunity(this.state.filters ?? {})\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: 'dynamic-preview', nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: 'dynamic-preview', name: 'Dynamic preview' },\n status: { loading: false, error: null, freshness: 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'user-authored') {\n if (!activeCommunity) throw new Error('user-authored mode requires an active community source')\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: activeCommunity.id, nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: activeCommunity.graphSourceId ?? activeCommunity.id, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err))\n this.setState({ status: { ...this.state.status, loading: false, error: e } })\n throw e\n }\n }\n\n setMode(nextMode: GraphSourceMode): void {\n this.beginTransition(nextMode, 'mode-change')\n this.setState({ mode: nextMode })\n void this.refresh()\n }\n\n setEmbeddingSetSelector(selector: EmbeddingSetSelector): void {\n this.beginTransition('topics', 'embedding-set-change')\n this.setState({ embeddingSetSelector: selector, mode: 'topics' })\n void this.refresh()\n }\n\n setCommunitySource(sourceId: string | null): void {\n this.communitySourceId = sourceId\n this.beginTransition(this.state.mode, 'community-source-change')\n void this.refresh()\n }\n\n /** Apply dynamic-search filters and switch into that mode (state only). */\n private applyFilters(nextFilters: CommunityFilterDefinition): void {\n this.beginTransition('dynamic-search', 'filter-change')\n this.setState({ filters: nextFilters, mode: 'dynamic-search' })\n }\n\n setFilters(nextFilters: CommunityFilterDefinition): void {\n this.applyFilters(nextFilters)\n void this.refresh()\n }\n\n async recompute(): Promise<void> {\n this.beginTransition(this.state.mode, 'recompute')\n if (this.state.mode === 'topics' && this.state.embeddingSetSelector) {\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({\n selector: this.state.embeddingSetSelector,\n source: 'live-only',\n })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n await this.refresh()\n }\n\n async previewDynamicCommunity(nextFilters: CommunityFilterDefinition): Promise<void> {\n this.applyFilters(nextFilters)\n await this.refresh()\n }\n\n async saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor> {\n const source = await this.communityRepo.saveCommunity(input)\n this.communitySourceId = source.id\n this.setState({ communitySource: source })\n return source\n }\n}\n","// @fortemi/graph — framework-agnostic graph presentation/projection helpers\n// plus the graph-source controller.\n//\n// Depends on @fortemi/core (the base data layer) and is consumed by\n// @fortemi/react and JS-only hosts. Dependency direction is the linear chain:\n// pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react. @fortemi/core never\n// imports @fortemi/graph.\n//\n// Pure projection helpers (layout, filtering, coloring, sizing, bounds,\n// neighborhood, snapshot) give React and JS-only hosts the shared logic to\n// render their own SVG/canvas views. `GraphController` adds the framework-\n// agnostic graph-source state machine (mode selection + load dispatch) on top\n// of @fortemi/core's repositories.\n//\n// Community *detection* intentionally lives in @fortemi/core (the base layer);\n// this package only renders/projects graphs it is given and orchestrates which\n// source to load.\n\nexport const VERSION = '2026.6.8'\n\nexport type {\n GraphNode,\n GraphEdge,\n GraphCommunity,\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraphNode,\n PositionedGraph,\n GraphBounds,\n ViewportTransform,\n} from './types.js'\n\nexport { computeDegrees, nodeRadius } from './degree.js'\nexport type { NodeRadiusOptions } from './degree.js'\n\nexport { COMMUNITY_COLORS, UNASSIGNED_COMMUNITY_COLOR, colorForCommunity } from './color.js'\n\nexport { filterCommunityGraph } from './filter.js'\nexport type { GraphFilter } from './filter.js'\n\nexport { layoutCommunityGraph } from './layout.js'\nexport type { LayoutOptions } from './layout.js'\n\nexport { computeGraphBounds, fitGraphToViewport } from './bounds.js'\nexport type { FitOptions } from './bounds.js'\n\nexport {\n buildAdjacency,\n neighborsOf,\n expandNeighborhood,\n subgraphForNodes,\n neighborhoodSubgraph,\n} from './neighborhood.js'\nexport type { ExpandOptions } from './neighborhood.js'\n\nexport {\n GRAPH_SNAPSHOT_VERSION,\n serializeGraphSnapshot,\n stringifyGraphSnapshot,\n deserializeGraphSnapshot,\n} from './serialize.js'\nexport type { GraphSnapshot, SerializeSnapshotOptions } from './serialize.js'\n\nexport { GraphController } from './controller.js'\nexport type {\n GraphControllerDb,\n GraphSourceMode,\n GraphLayoutState,\n GraphTransitionState,\n GraphControllerStatus,\n GraphSourceRef,\n GraphControllerState,\n GraphControllerOptions,\n GraphControllerListener,\n} from './controller.js'\n"]}
1
+ {"version":3,"sources":["../src/degree.ts","../src/color.ts","../src/filter.ts","../src/rng.ts","../src/layout.ts","../src/bounds.ts","../src/neighborhood.ts","../src/serialize.ts","../src/controller.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { CommunityGraph } from './types.js'\n\n/** Count the (undirected) degree of every node, including isolated nodes (0). */\nexport function computeDegrees(graph: CommunityGraph): Map<string, number> {\n const degree = new Map<string, number>()\n for (const node of graph.nodes) degree.set(node.id, 0)\n for (const edge of graph.edges) {\n degree.set(edge.source, (degree.get(edge.source) ?? 0) + 1)\n degree.set(edge.target, (degree.get(edge.target) ?? 0) + 1)\n }\n return degree\n}\n\nexport interface NodeRadiusOptions {\n /** Radius of a degree-0 node. */\n base?: number\n /** Pixels added per unit of degree. */\n perDegree?: number\n /** Hard floor on the radius. */\n min?: number\n /** Hard ceiling on the radius. */\n max?: number\n}\n\n/**\n * Map a node degree to a render radius. Defaults match the values historically\n * used by the React `GraphView`: `clamp(5 + degree * 1.5, 5, 16)`.\n */\nexport function nodeRadius(degree: number, options: NodeRadiusOptions = {}): number {\n const base = options.base ?? 5\n const perDegree = options.perDegree ?? 1.5\n const min = options.min ?? 5\n const max = options.max ?? 16\n return Math.max(min, Math.min(max, base + degree * perDegree))\n}\n","/** Default qualitative palette for community coloring (8 hues). */\nexport const COMMUNITY_COLORS = [\n '#2f6fbb',\n '#d97706',\n '#218838',\n '#7c3aed',\n '#c2410c',\n '#0f766e',\n '#be185d',\n '#4b5563',\n] as const\n\n/** Color used for nodes that are not assigned to any community. */\nexport const UNASSIGNED_COMMUNITY_COLOR = '#64748b'\n\n/**\n * Deterministically assign a color to a community id by hashing the id into the\n * palette. The same id always maps to the same color across runs and hosts.\n * Pass a custom `palette` to theme the output. Matches the historical\n * `GraphView` color assignment.\n */\nexport function colorForCommunity(\n communityId: string | undefined,\n palette: readonly string[] = COMMUNITY_COLORS,\n unassignedColor: string = UNASSIGNED_COMMUNITY_COLOR,\n): string {\n if (!communityId) return unassignedColor\n if (palette.length === 0) return unassignedColor\n let hash = 0\n for (const char of communityId) hash = (hash * 31 + char.charCodeAt(0)) | 0\n return palette[Math.abs(hash) % palette.length]\n}\n","import type { CommunityGraph, GraphNode } from './types.js'\n\nexport interface GraphFilter {\n /** Keep only nodes belonging to these communities (by community id). */\n communityIds?: string[]\n /** Keep only edges whose `kind` is in this set. */\n edgeKinds?: string[]\n /** Explicit allow-list of node ids. Useful for privacy / visibility filtering. */\n nodeIds?: string[]\n /**\n * Arbitrary per-node predicate evaluated against the (allow-listed) nodes.\n * Returning `false` removes the node and any edge touching it. Hosts whose\n * nodes carry extra metadata (privacy class, type, etc.) can filter on it here.\n */\n nodePredicate?: (node: GraphNode) => boolean\n}\n\n/**\n * Produce a new {@link CommunityGraph} containing only the nodes, edges, and\n * community memberships permitted by `filter`. Pure — the input is not mutated.\n *\n * Filtering precedence:\n * 1. `communityIds` (if non-empty) restricts the candidate node set to members\n * of those communities; otherwise `nodeIds` (if given) is the candidate set,\n * else all nodes.\n * 2. `nodePredicate` further narrows the candidate set.\n * 3. Edges survive only when both endpoints survive and `edgeKinds` permits.\n * 4. Empty communities are dropped.\n */\nexport function filterCommunityGraph(\n graph: CommunityGraph | null | undefined,\n filter?: GraphFilter,\n): CommunityGraph {\n if (!graph) return { nodes: [], edges: [], communities: [] }\n\n const nodeIds = new Set(filter?.nodeIds ?? graph.nodes.map((node) => node.id))\n if (filter?.communityIds?.length) {\n const allowedCommunities = new Set(filter.communityIds)\n nodeIds.clear()\n for (const community of graph.communities) {\n if (allowedCommunities.has(community.id)) {\n for (const nodeId of community.nodes) nodeIds.add(nodeId)\n }\n }\n }\n\n const predicate = filter?.nodePredicate\n if (predicate) {\n for (const node of graph.nodes) {\n if (nodeIds.has(node.id) && !predicate(node)) nodeIds.delete(node.id)\n }\n }\n\n const edgeKinds = filter?.edgeKinds ? new Set(filter.edgeKinds) : null\n const nodes = graph.nodes.filter((node) => nodeIds.has(node.id))\n const edges = graph.edges.filter((edge) => (\n nodeIds.has(edge.source)\n && nodeIds.has(edge.target)\n && (!edgeKinds || edgeKinds.has(edge.kind ?? ''))\n ))\n const communities = graph.communities\n .map((community) => ({ ...community, nodes: community.nodes.filter((nodeId) => nodeIds.has(nodeId)) }))\n .filter((community) => community.nodes.length > 0)\n return { nodes, edges, communities }\n}\n","// Tiny deterministic PRNG (mulberry32). Used by the force layout to seed\n// initial jitter so that identical (seed, graph, options) always yields\n// identical coordinates — no Math.random, no time, no platform variance.\n\n/**\n * Create a deterministic pseudo-random generator from a 32-bit integer seed.\n * Returns a function producing floats in [0, 1). Identical seeds yield\n * identical sequences across platforms.\n */\nexport function mulberry32(seed: number): () => number {\n // Coerce to a non-zero unsigned 32-bit integer so seed 0 still advances.\n let a = (seed >>> 0) || 0x9e3779b9\n return function next(): number {\n a |= 0\n a = (a + 0x6d2b79f5) | 0\n let t = Math.imul(a ^ (a >>> 15), 1 | a)\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n","import { computeDegrees, nodeRadius as defaultNodeRadius, type NodeRadiusOptions } from './degree.js'\nimport { mulberry32 } from './rng.js'\nimport type {\n CommunityGraph,\n GraphLayoutAlgorithm,\n GraphNode,\n PositionedCommunity,\n PositionedGraph,\n PositionedGraphNode,\n} from './types.js'\n\n/**\n * How to derive each node's render radius. Either a fixed pixel value, a set of\n * {@link NodeRadiusOptions} fed to the default degree→radius mapping, or a full\n * resolver `(degree, node) => number`.\n */\nexport type NodeRadiusResolver =\n | number\n | NodeRadiusOptions\n | ((degree: number, node: GraphNode) => number)\n\nexport interface LayoutOptions {\n /** Layout algorithm. `force` runs deterministic settlement; the others are closed-form. */\n algorithm?: GraphLayoutAlgorithm\n width?: number\n height?: number\n /** Seed for the deterministic PRNG used by `force` initial jitter. Identical seed ⇒ identical output. */\n seed?: number\n /** Number of settlement iterations for the `force` algorithm. */\n ticks?: number\n /** Per-node render radius (default: degree-derived, clamped 5..16). */\n nodeRadius?: NodeRadiusResolver\n /** Target edge length in px for the spring force (`force` only). */\n linkDistance?: number\n /** Spring stiffness 0..1 (`force` only). */\n linkStrength?: number\n /** Repulsion magnitude; negative pushes nodes apart (`force` only). */\n chargeStrength?: number\n /** Extra spacing beyond `r_i + r_j` when resolving collisions (`force` only). */\n collisionPadding?: number\n /** Pull toward the node's community centroid, 0..1 (`force` only). */\n communityStrength?: number\n /** Keep every node center at least this many px from each canvas edge. */\n boundsPadding?: number\n}\n\nconst DEFAULTS = {\n width: 760,\n height: 460,\n seed: 1,\n ticks: 300,\n linkDistance: 60,\n linkStrength: 0.08,\n chargeStrength: -240,\n collisionPadding: 2,\n communityStrength: 0.05,\n boundsPadding: 24,\n} as const\n\n/** Mild global pull toward the canvas center so disconnected nodes don't drift. */\nconst GRAVITY = 0.02\n\nfunction resolveRadius(\n resolver: NodeRadiusResolver | undefined,\n degree: number,\n node: GraphNode,\n): number {\n if (resolver === undefined) return defaultNodeRadius(degree)\n if (typeof resolver === 'number') return resolver\n if (typeof resolver === 'function') return resolver(degree, node)\n return defaultNodeRadius(degree, resolver)\n}\n\n/** Clamp `value` into `[lo, hi]`, collapsing to the midpoint if the range inverts. */\nfunction clampToRange(value: number, lo: number, hi: number): number {\n if (lo > hi) return (lo + hi) / 2\n return Math.max(lo, Math.min(hi, value))\n}\n\n/**\n * Deterministically position the nodes of a {@link CommunityGraph} in 2D space.\n *\n * For the default `force` algorithm this runs a fixed-iteration, seeded force\n * settlement (spring link attraction, charge repulsion, collision spacing,\n * community cohesion, centering, and bounds clamping). It is fully synchronous\n * and headless — no animation frames — so the same `(graph, options)` always\n * yields identical coordinates, safe for snapshot rendering and SSR. The\n * `radial`, `community`, and `manual` algorithms remain closed-form.\n *\n * Every node is annotated with its render radius `r`, degree, and resolved\n * community; the result also carries community centroids over the final layout.\n */\nexport function layoutCommunityGraph(\n graph: CommunityGraph,\n options: LayoutOptions = {},\n): PositionedGraph {\n const algorithm = options.algorithm ?? 'force'\n const width = options.width ?? DEFAULTS.width\n const height = options.height ?? DEFAULTS.height\n const boundsPadding = options.boundsPadding ?? DEFAULTS.boundsPadding\n\n const degree = computeDegrees(graph)\n\n const communityByNode = new Map<string, string>()\n for (const community of graph.communities) {\n for (const nodeId of community.nodes) {\n if (!communityByNode.has(nodeId)) communityByNode.set(nodeId, community.id)\n }\n }\n\n const centerX = width / 2\n const centerY = height / 2\n const radius = Math.max(40, Math.min(width, height) * 0.38)\n const n = graph.nodes.length\n\n // Per-node render radius (stable, used by force spacing and by consumers).\n const r = graph.nodes.map((node) =>\n resolveRadius(options.nodeRadius, degree.get(node.id) ?? 0, node),\n )\n\n // Index from node id → array position (edges reference nodes by id).\n const indexOf = new Map<string, number>()\n graph.nodes.forEach((node, i) => indexOf.set(node.id, i))\n\n // Community ring anchors — used both as closed-form positions and as the\n // initial seed for force settlement, keeping clusters legible.\n const communityIndexById = new Map<string, number>()\n graph.communities.forEach((community, i) => communityIndexById.set(community.id, i))\n\n const x = new Float64Array(n)\n const y = new Float64Array(n)\n\n // --- Initial / closed-form positions ---------------------------------------\n graph.nodes.forEach((node, index) => {\n const angle = (Math.PI * 2 * index) / Math.max(1, n)\n const communityId = communityByNode.get(node.id)\n const communityIndex = communityId !== undefined ? communityIndexById.get(communityId) ?? -1 : -1\n const communityAngle =\n (Math.PI * 2 * Math.max(0, communityIndex)) / Math.max(1, graph.communities.length)\n const useCommunityOrbit = algorithm === 'community' || algorithm === 'force'\n const communityRadius = algorithm === 'community' ? radius * 0.55 : radius\n const localRadius =\n algorithm === 'force'\n ? radius * (0.7 + ((degree.get(node.id) ?? 0) % 4) * 0.08)\n : radius\n\n if (useCommunityOrbit && communityIndex >= 0) {\n x[index] = centerX + Math.cos(communityAngle) * communityRadius + Math.cos(angle) * 46\n y[index] = centerY + Math.sin(communityAngle) * communityRadius + Math.sin(angle) * 46\n } else {\n x[index] = centerX + Math.cos(angle) * localRadius\n y[index] =\n centerY + Math.sin(angle) * (algorithm === 'radial' ? radius : localRadius * 0.72)\n }\n })\n\n // --- Force settlement (force algorithm only) -------------------------------\n if (algorithm === 'force' && n > 1) {\n const ticks = Math.max(0, Math.floor(options.ticks ?? DEFAULTS.ticks))\n const seed = options.seed ?? DEFAULTS.seed\n const linkDistance = options.linkDistance ?? DEFAULTS.linkDistance\n const linkStrength = options.linkStrength ?? DEFAULTS.linkStrength\n const chargeStrength = options.chargeStrength ?? DEFAULTS.chargeStrength\n const collisionPadding = options.collisionPadding ?? DEFAULTS.collisionPadding\n const communityStrength = options.communityStrength ?? DEFAULTS.communityStrength\n\n const rng = mulberry32(seed)\n // Deterministic jitter so no two seeded nodes coincide (avoids div-by-zero).\n for (let i = 0; i < n; i++) {\n x[i] += (rng() - 0.5) * 8\n y[i] += (rng() - 0.5) * 8\n }\n\n // Pre-resolve edge endpoints to array indices once.\n const links: Array<{ a: number; b: number; weight: number }> = []\n for (const edge of graph.edges) {\n const a = indexOf.get(edge.source)\n const b = indexOf.get(edge.target)\n if (a === undefined || b === undefined || a === b) continue\n links.push({ a, b, weight: edge.weight > 0 ? edge.weight : 1 })\n }\n\n // Community membership as array indices, for centroid cohesion.\n const communityOf = graph.nodes.map((node) => communityByNode.get(node.id))\n const communityIds = graph.communities.map((c) => c.id)\n\n const dispX = new Float64Array(n)\n const dispY = new Float64Array(n)\n const chargeCutoff = linkDistance * 6\n const cooling = ticks > 0 ? Math.pow(0.001, 1 / ticks) : 1\n let alpha = 1\n\n for (let t = 0; t < ticks; t++) {\n dispX.fill(0)\n dispY.fill(0)\n\n // Spring link attraction toward linkDistance.\n for (const link of links) {\n let dx = x[link.b] - x[link.a]\n let dy = y[link.b] - y[link.a]\n const dist = Math.max(0.01, Math.hypot(dx, dy))\n const force = ((dist - linkDistance) / dist) * linkStrength * link.weight\n dx *= force\n dy *= force\n dispX[link.a] += dx\n dispY[link.a] += dy\n dispX[link.b] -= dx\n dispY[link.b] -= dy\n }\n\n // Pairwise charge repulsion (with a distance cutoff for dense graphs).\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const dx = x[i] - x[j]\n const dy = y[i] - y[j]\n const dist = Math.max(0.01, Math.hypot(dx, dy))\n if (dist > chargeCutoff) continue\n // chargeStrength is negative ⇒ repulsion magnitude is positive.\n const mag = -chargeStrength / dist\n const ux = (dx / dist) * mag\n const uy = (dy / dist) * mag\n dispX[i] += ux\n dispY[i] += uy\n dispX[j] -= ux\n dispY[j] -= uy\n }\n }\n\n // Community cohesion (toward centroid) + global centering gravity.\n if (communityStrength > 0 && communityIds.length > 0) {\n const cx = new Float64Array(communityIds.length)\n const cy = new Float64Array(communityIds.length)\n const cn = new Int32Array(communityIds.length)\n const idxById = new Map<string, number>()\n communityIds.forEach((id, i) => idxById.set(id, i))\n for (let i = 0; i < n; i++) {\n const cid = communityOf[i]\n if (cid === undefined) continue\n const ci = idxById.get(cid)\n if (ci === undefined) continue\n cx[ci] += x[i]\n cy[ci] += y[i]\n cn[ci] += 1\n }\n for (let i = 0; i < n; i++) {\n const cid = communityOf[i]\n if (cid === undefined) continue\n const ci = idxById.get(cid)\n if (ci === undefined || cn[ci] === 0) continue\n dispX[i] += (cx[ci] / cn[ci] - x[i]) * communityStrength\n dispY[i] += (cy[ci] / cn[ci] - y[i]) * communityStrength\n }\n }\n for (let i = 0; i < n; i++) {\n dispX[i] += (centerX - x[i]) * GRAVITY\n dispY[i] += (centerY - y[i]) * GRAVITY\n }\n\n // Apply soft forces, cooled by alpha.\n for (let i = 0; i < n; i++) {\n x[i] += dispX[i] * alpha\n y[i] += dispY[i] * alpha\n }\n\n // Hard collision resolution (full strength) so nodes don't overlap.\n for (let i = 0; i < n; i++) {\n for (let j = i + 1; j < n; j++) {\n const dx = x[i] - x[j]\n const dy = y[i] - y[j]\n const dist = Math.max(0.01, Math.hypot(dx, dy))\n const minDist = r[i] + r[j] + collisionPadding\n if (dist >= minDist) continue\n const push = (minDist - dist) / 2\n const ux = (dx / dist) * push\n const uy = (dy / dist) * push\n x[i] += ux\n y[i] += uy\n x[j] -= ux\n y[j] -= uy\n }\n }\n\n // Clamp inside the padded canvas every tick.\n for (let i = 0; i < n; i++) {\n x[i] = clampToRange(x[i], boundsPadding + r[i], width - boundsPadding - r[i])\n y[i] = clampToRange(y[i], boundsPadding + r[i], height - boundsPadding - r[i])\n }\n\n alpha *= cooling\n }\n } else {\n // Closed-form algorithms: still honor the bounds clamp.\n for (let i = 0; i < n; i++) {\n x[i] = clampToRange(x[i], boundsPadding + r[i], width - boundsPadding - r[i])\n y[i] = clampToRange(y[i], boundsPadding + r[i], height - boundsPadding - r[i])\n }\n }\n\n // --- Build the result ------------------------------------------------------\n const nodes = graph.nodes.map((node, index): PositionedGraphNode => ({\n ...node,\n x: x[index],\n y: y[index],\n r: r[index],\n degree: degree.get(node.id) ?? 0,\n communityId: communityByNode.get(node.id),\n }))\n\n const nodeIndex = new Map(nodes.map((node) => [node.id, node]))\n\n const communities: PositionedCommunity[] = graph.communities.map((community) => {\n let sx = 0\n let sy = 0\n let size = 0\n for (const nodeId of community.nodes) {\n const positioned = nodeIndex.get(nodeId)\n if (!positioned) continue\n sx += positioned.x\n sy += positioned.y\n size += 1\n }\n return size > 0\n ? { id: community.id, x: sx / size, y: sy / size, size }\n : { id: community.id, x: centerX, y: centerY, size: 0 }\n })\n\n return { nodes, edges: graph.edges, nodeIndex, communities }\n}\n","import type { GraphBounds, PositionedGraphNode, ViewportTransform } from './types.js'\n\nconst EMPTY_BOUNDS: GraphBounds = {\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0,\n centerX: 0,\n centerY: 0,\n}\n\n/**\n * Compute the axis-aligned bounding box around a set of positioned nodes.\n * Returns an all-zero box for an empty input.\n */\nexport function computeGraphBounds(\n nodes: ReadonlyArray<Pick<PositionedGraphNode, 'x' | 'y'>>,\n): GraphBounds {\n if (nodes.length === 0) return { ...EMPTY_BOUNDS }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const node of nodes) {\n if (node.x < minX) minX = node.x\n if (node.y < minY) minY = node.y\n if (node.x > maxX) maxX = node.x\n if (node.y > maxY) maxY = node.y\n }\n return {\n minX,\n minY,\n maxX,\n maxY,\n width: maxX - minX,\n height: maxY - minY,\n centerX: (minX + maxX) / 2,\n centerY: (minY + maxY) / 2,\n }\n}\n\nexport interface FitOptions {\n /** Uniform padding (in viewport units) to leave around the graph. */\n padding?: number\n /** Clamp the computed scale to this minimum. */\n minScale?: number\n /** Clamp the computed scale to this maximum. */\n maxScale?: number\n}\n\n/**\n * Compute a {@link ViewportTransform} (uniform scale + translation) that fits\n * `bounds` centered within a `viewport` of the given width/height. Apply the\n * result as `translate(offsetX, offsetY) scale(scale)` in SVG/canvas space.\n */\nexport function fitGraphToViewport(\n bounds: GraphBounds,\n viewport: { width: number; height: number },\n options: FitOptions = {},\n): ViewportTransform {\n const padding = options.padding ?? 0\n const minScale = options.minScale ?? 0\n const maxScale = options.maxScale ?? Infinity\n\n const availableWidth = Math.max(0, viewport.width - padding * 2)\n const availableHeight = Math.max(0, viewport.height - padding * 2)\n\n let scale = 1\n if (bounds.width > 0 || bounds.height > 0) {\n const scaleX = bounds.width > 0 ? availableWidth / bounds.width : Infinity\n const scaleY = bounds.height > 0 ? availableHeight / bounds.height : Infinity\n scale = Math.min(scaleX, scaleY)\n }\n if (!Number.isFinite(scale) || scale <= 0) scale = 1\n scale = Math.max(minScale, Math.min(maxScale, scale))\n\n const offsetX = viewport.width / 2 - bounds.centerX * scale\n const offsetY = viewport.height / 2 - bounds.centerY * scale\n return { scale, offsetX, offsetY }\n}\n","import { filterCommunityGraph } from './filter.js'\nimport type { CommunityGraph } from './types.js'\n\n/** Build an undirected adjacency map from a graph's edges. */\nexport function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>> {\n const adjacency = new Map<string, Set<string>>()\n const ensure = (id: string) => {\n let set = adjacency.get(id)\n if (!set) {\n set = new Set<string>()\n adjacency.set(id, set)\n }\n return set\n }\n for (const node of graph.nodes) ensure(node.id)\n for (const edge of graph.edges) {\n ensure(edge.source).add(edge.target)\n ensure(edge.target).add(edge.source)\n }\n return adjacency\n}\n\n/** Return the set of node ids directly adjacent to `nodeId` (excludes itself). */\nexport function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string> {\n return new Set(buildAdjacency(graph).get(nodeId) ?? [])\n}\n\nexport interface ExpandOptions {\n /** How many edge-hops to traverse from the seeds. Default 1. */\n depth?: number\n /** Include the seed ids in the result. Default true. */\n includeSeeds?: boolean\n}\n\n/**\n * Breadth-first expansion of one or more seed nodes out to `depth` hops.\n * Returns the set of reached node ids (seeds included by default).\n */\nexport function expandNeighborhood(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): Set<string> {\n const depth = options.depth ?? 1\n const includeSeeds = options.includeSeeds ?? true\n const adjacency = buildAdjacency(graph)\n\n const seen = new Set<string>()\n let frontier = new Set<string>()\n for (const seed of seeds) {\n if (adjacency.has(seed) || depth === 0) {\n seen.add(seed)\n frontier.add(seed)\n }\n }\n\n for (let hop = 0; hop < depth; hop++) {\n const next = new Set<string>()\n for (const id of frontier) {\n for (const neighbor of adjacency.get(id) ?? []) {\n if (!seen.has(neighbor)) {\n seen.add(neighbor)\n next.add(neighbor)\n }\n }\n }\n if (next.size === 0) break\n frontier = next\n }\n\n if (!includeSeeds) {\n for (const seed of seeds) seen.delete(seed)\n }\n return seen\n}\n\n/**\n * Extract the induced subgraph over `nodeIds` — a {@link CommunityGraph}\n * containing only those nodes, the edges between them, and the trimmed\n * community memberships. Thin wrapper over {@link filterCommunityGraph}.\n */\nexport function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph {\n return filterCommunityGraph(graph, { nodeIds: Array.from(nodeIds) })\n}\n\n/**\n * Convenience: the induced subgraph of a seed node plus its `depth`-hop\n * neighborhood. Useful for \"expand selection\" interactions.\n */\nexport function neighborhoodSubgraph(\n graph: CommunityGraph,\n seeds: Iterable<string>,\n options: ExpandOptions = {},\n): CommunityGraph {\n return subgraphForNodes(graph, expandNeighborhood(graph, seeds, options))\n}\n","import type { CommunityGraph, GraphLayoutAlgorithm } from './types.js'\n\n/** Current on-disk version for {@link GraphSnapshot}. */\nexport const GRAPH_SNAPSHOT_VERSION = 1\n\n/**\n * A self-contained, serializable representation of a community graph plus the\n * layout intent used to render it. Designed to be written to a static JSON file\n * and consumed by a JS-only host (e.g. a documentation site) without recomputing\n * the graph.\n */\nexport interface GraphSnapshot {\n version: number\n graph: CommunityGraph\n layout?: {\n algorithm: GraphLayoutAlgorithm\n width: number\n height: number\n }\n generatedAt?: string\n}\n\nexport interface SerializeSnapshotOptions {\n layout?: GraphSnapshot['layout']\n /** ISO timestamp to stamp into the snapshot. Omit for reproducible output. */\n generatedAt?: string\n}\n\nfunction sortedGraph(graph: CommunityGraph): CommunityGraph {\n return {\n nodes: [...graph.nodes].sort((a, b) => a.id.localeCompare(b.id)),\n edges: [...graph.edges].sort((a, b) => (\n a.source.localeCompare(b.source)\n || a.target.localeCompare(b.target)\n || (a.kind ?? '').localeCompare(b.kind ?? '')\n )),\n communities: [...graph.communities]\n .map((community) => ({ ...community, nodes: [...community.nodes].sort() }))\n .sort((a, b) => a.id.localeCompare(b.id)),\n }\n}\n\n/**\n * Build a deterministic {@link GraphSnapshot} from a graph. Nodes, edges, and\n * community members are sorted so that equal graphs serialize identically\n * (stable diffs, content-addressable caching).\n */\nexport function serializeGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): GraphSnapshot {\n const snapshot: GraphSnapshot = {\n version: GRAPH_SNAPSHOT_VERSION,\n graph: sortedGraph(graph),\n }\n if (options.layout) snapshot.layout = options.layout\n if (options.generatedAt) snapshot.generatedAt = options.generatedAt\n return snapshot\n}\n\n/** Stable JSON string for a graph snapshot. */\nexport function stringifyGraphSnapshot(\n graph: CommunityGraph,\n options: SerializeSnapshotOptions = {},\n): string {\n return JSON.stringify(serializeGraphSnapshot(graph, options))\n}\n\n/**\n * Validate and unwrap a {@link GraphSnapshot} (or its JSON string) back into a\n * {@link CommunityGraph}. Throws on shape/version mismatch.\n */\nexport function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph {\n const snapshot: unknown = typeof input === 'string' ? JSON.parse(input) : input\n if (!snapshot || typeof snapshot !== 'object') {\n throw new Error('invalid graph snapshot: not an object')\n }\n const candidate = snapshot as Partial<GraphSnapshot>\n if (candidate.version !== GRAPH_SNAPSHOT_VERSION) {\n throw new Error(`unsupported graph snapshot version: ${String(candidate.version)}`)\n }\n const graph = candidate.graph\n if (\n !graph\n || !Array.isArray(graph.nodes)\n || !Array.isArray(graph.edges)\n || !Array.isArray(graph.communities)\n ) {\n throw new Error('invalid graph snapshot: missing graph nodes/edges/communities')\n }\n return graph\n}\n","// Framework-agnostic graph-source controller.\n//\n// Owns the graph \"source mode\" state machine (citations | topics | precomputed\n// | dynamic-search | user-authored), transition tracking, and the mode→fetch\n// dispatch. The capability previously lived inside the React `useGraphController`\n// hook; it now lives here so JS-only hosts can drive the same logic without\n// React. The React hook is a thin `useSyncExternalStore` adapter over this class.\n//\n// This module depends on @fortemi/core for the repositories and shared types.\n// @fortemi/core never depends on @fortemi/graph — the dependency direction is\n// the linear chain: pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react.\n\nimport {\n CommunitiesRepository,\n GraphRepository,\n type CommunityCreateInput,\n type CommunityFilterDefinition,\n type CommunityGraph,\n type CommunitySourceDescriptor,\n type DatabaseClient,\n type EmbeddingSetSelector,\n type QueryExecutor,\n type SimilarityGraphResult,\n} from '@fortemi/core'\nimport type { GraphLayoutAlgorithm } from './types.js'\n\n/** A database handle accepted by the underlying repositories. */\nexport type GraphControllerDb = QueryExecutor & DatabaseClient\n\nexport type GraphSourceMode =\n | 'citations'\n | 'topics'\n | 'precomputed'\n | 'dynamic-search'\n | 'user-authored'\n\nexport interface GraphLayoutState {\n algorithm: GraphLayoutAlgorithm\n pinSelectedNodes?: boolean\n preserveViewport?: boolean\n communitySpacing?: number\n}\n\nexport interface GraphTransitionState {\n fromMode?: GraphSourceMode\n toMode: GraphSourceMode\n reason: 'mode-change' | 'embedding-set-change' | 'community-source-change' | 'filter-change' | 'recompute'\n startedAt: string\n}\n\nexport interface GraphControllerStatus {\n loading: boolean\n error: Error | null\n freshness: 'fresh' | 'stale' | 'unknown' | null\n cache: SimilarityGraphResult['cache'] | null\n}\n\nexport type GraphSourceRef = SimilarityGraphResult['graphSource'] | { id: string; name: string }\n\n/** The full, framework-agnostic controller state surfaced to subscribers. */\nexport interface GraphControllerState {\n mode: GraphSourceMode\n graph: CommunityGraph | null\n graphSource?: GraphSourceRef\n communitySource?: CommunitySourceDescriptor\n embeddingSetSelector?: EmbeddingSetSelector\n filters?: CommunityFilterDefinition\n layout: GraphLayoutState\n status: GraphControllerStatus\n transition?: GraphTransitionState\n}\n\nexport interface GraphControllerOptions {\n initialMode?: GraphSourceMode\n initialEmbeddingSetSelector?: EmbeddingSetSelector\n initialCommunitySourceId?: string\n initialFilters?: CommunityFilterDefinition\n layout?: Partial<GraphLayoutState>\n}\n\nconst DEFAULT_LAYOUT: GraphLayoutState = {\n algorithm: 'force',\n preserveViewport: true,\n communitySpacing: 1,\n}\n\nexport type GraphControllerListener = (state: GraphControllerState) => void\n\n/**\n * Drives graph-source selection and loading independent of any UI framework.\n *\n * Construct with pre-built repositories (handy for tests) or via\n * {@link GraphController.fromDb}. Subscribe with {@link subscribe} and read the\n * current state with {@link getState}; call {@link start} once to trigger the\n * initial load. Setters update state and schedule a refresh, mirroring the\n * effect-driven behavior of the original React hook.\n */\nexport class GraphController {\n private readonly graphRepo: GraphRepository\n private readonly communityRepo: CommunitiesRepository\n private readonly listeners = new Set<GraphControllerListener>()\n private communitySourceId: string | null\n private state: GraphControllerState\n\n /** Build a controller from a database handle (production path). */\n static fromDb(db: GraphControllerDb, options: GraphControllerOptions = {}): GraphController {\n return new GraphController(new GraphRepository(db), new CommunitiesRepository(db), options)\n }\n\n constructor(\n graphRepo: GraphRepository,\n communityRepo: CommunitiesRepository,\n options: GraphControllerOptions = {},\n ) {\n this.graphRepo = graphRepo\n this.communityRepo = communityRepo\n this.communitySourceId = options.initialCommunitySourceId ?? null\n this.state = {\n mode: options.initialMode ?? 'citations',\n graph: null,\n graphSource: undefined,\n communitySource: undefined,\n embeddingSetSelector: options.initialEmbeddingSetSelector,\n filters: options.initialFilters,\n layout: { ...DEFAULT_LAYOUT, ...options.layout },\n status: { loading: false, error: null, freshness: null, cache: null },\n transition: undefined,\n }\n }\n\n getState(): GraphControllerState {\n return this.state\n }\n\n subscribe(listener: GraphControllerListener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /** Run the initial load. Call once after construction (the hook calls this on mount). */\n async start(): Promise<void> {\n await this.refresh()\n }\n\n private setState(patch: Partial<GraphControllerState>): void {\n this.state = { ...this.state, ...patch }\n for (const listener of this.listeners) listener(this.state)\n }\n\n private beginTransition(\n toMode: GraphSourceMode,\n reason: GraphTransitionState['reason'],\n fromMode: GraphSourceMode = this.state.mode,\n ): void {\n this.setState({ transition: { fromMode, toMode, reason, startedAt: new Date().toISOString() } })\n }\n\n async refresh(): Promise<void> {\n try {\n this.setState({ status: { ...this.state.status, loading: true, error: null } })\n const sources = await this.communityRepo.listCommunitySources()\n const activeCommunity = this.communitySourceId\n ? sources.find((source) => source.id === this.communitySourceId)\n : undefined\n this.setState({ communitySource: activeCommunity })\n\n const mode = this.state.mode\n\n if (mode === 'citations') {\n const nextGraph = await this.graphRepo.buildLinkGraph()\n this.setState({\n graph: nextGraph,\n graphSource: { id: 'citations', name: 'Citation graph' },\n status: { loading: false, error: null, freshness: 'fresh', cache: null },\n })\n return\n }\n\n if (mode === 'topics') {\n const selector = this.state.embeddingSetSelector\n if (!selector) throw new Error('topics mode requires an embedding-set selector')\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({ selector })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n\n if (mode === 'precomputed') {\n if (!activeCommunity?.graphSourceId) {\n throw new Error('precomputed mode requires a community source with graphSourceId')\n }\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n const nextGraph = await this.graphRepo.loadGraphArtifact(\n activeCommunity.graphSourceId,\n assignments.map((assignment) => assignment.noteId),\n )\n this.setState({\n graph: nextGraph,\n graphSource: { id: activeCommunity.graphSourceId, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'dynamic-search') {\n const assignments = await this.communityRepo.previewDynamicCommunity(this.state.filters ?? {})\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: 'dynamic-preview', nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: 'dynamic-preview', name: 'Dynamic preview' },\n status: { loading: false, error: null, freshness: 'unknown', cache: null },\n })\n return\n }\n\n if (mode === 'user-authored') {\n if (!activeCommunity) throw new Error('user-authored mode requires an active community source')\n const assignments = await this.communityRepo.getCommunityAssignments(activeCommunity.id)\n this.setState({\n graph: {\n nodes: assignments.map((assignment) => ({ id: assignment.noteId })),\n edges: [],\n communities: [{ id: activeCommunity.id, nodes: assignments.map((assignment) => assignment.noteId) }],\n },\n graphSource: { id: activeCommunity.graphSourceId ?? activeCommunity.id, name: activeCommunity.name },\n status: { loading: false, error: null, freshness: activeCommunity.freshness ?? 'unknown', cache: null },\n })\n }\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err))\n this.setState({ status: { ...this.state.status, loading: false, error: e } })\n throw e\n }\n }\n\n setMode(nextMode: GraphSourceMode): void {\n this.beginTransition(nextMode, 'mode-change')\n this.setState({ mode: nextMode })\n void this.refresh()\n }\n\n setEmbeddingSetSelector(selector: EmbeddingSetSelector): void {\n this.beginTransition('topics', 'embedding-set-change')\n this.setState({ embeddingSetSelector: selector, mode: 'topics' })\n void this.refresh()\n }\n\n setCommunitySource(sourceId: string | null): void {\n this.communitySourceId = sourceId\n this.beginTransition(this.state.mode, 'community-source-change')\n void this.refresh()\n }\n\n /** Apply dynamic-search filters and switch into that mode (state only). */\n private applyFilters(nextFilters: CommunityFilterDefinition): void {\n this.beginTransition('dynamic-search', 'filter-change')\n this.setState({ filters: nextFilters, mode: 'dynamic-search' })\n }\n\n setFilters(nextFilters: CommunityFilterDefinition): void {\n this.applyFilters(nextFilters)\n void this.refresh()\n }\n\n async recompute(): Promise<void> {\n this.beginTransition(this.state.mode, 'recompute')\n if (this.state.mode === 'topics' && this.state.embeddingSetSelector) {\n const result = await this.graphRepo.buildOrLoadSimilarityGraph({\n selector: this.state.embeddingSetSelector,\n source: 'live-only',\n })\n this.setState({\n graph: result.graph,\n graphSource: result.graphSource,\n status: { loading: false, error: null, freshness: result.freshness, cache: result.cache },\n })\n return\n }\n await this.refresh()\n }\n\n async previewDynamicCommunity(nextFilters: CommunityFilterDefinition): Promise<void> {\n this.applyFilters(nextFilters)\n await this.refresh()\n }\n\n async saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor> {\n const source = await this.communityRepo.saveCommunity(input)\n this.communitySourceId = source.id\n this.setState({ communitySource: source })\n return source\n }\n}\n","// @fortemi/graph — framework-agnostic graph presentation/projection helpers\n// plus the graph-source controller.\n//\n// Depends on @fortemi/core (the base data layer) and is consumed by\n// @fortemi/react and JS-only hosts. Dependency direction is the linear chain:\n// pglite ← @fortemi/core ← @fortemi/graph ← @fortemi/react. @fortemi/core never\n// imports @fortemi/graph.\n//\n// Pure projection helpers (layout, filtering, coloring, sizing, bounds,\n// neighborhood, snapshot) give React and JS-only hosts the shared logic to\n// render their own SVG/canvas views. `GraphController` adds the framework-\n// agnostic graph-source state machine (mode selection + load dispatch) on top\n// of @fortemi/core's repositories.\n//\n// Community *detection* intentionally lives in @fortemi/core (the base layer);\n// this package only renders/projects graphs it is given and orchestrates which\n// source to load.\n\nexport const VERSION = '2026.7.0'\n\nexport type {\n GraphNode,\n GraphEdge,\n GraphCommunity,\n CommunityGraph,\n GraphLayoutAlgorithm,\n PositionedGraphNode,\n PositionedCommunity,\n PositionedGraph,\n GraphBounds,\n ViewportTransform,\n} from './types.js'\n\nexport { computeDegrees, nodeRadius } from './degree.js'\nexport type { NodeRadiusOptions } from './degree.js'\n\nexport { COMMUNITY_COLORS, UNASSIGNED_COMMUNITY_COLOR, colorForCommunity } from './color.js'\n\nexport { filterCommunityGraph } from './filter.js'\nexport type { GraphFilter } from './filter.js'\n\nexport { layoutCommunityGraph } from './layout.js'\nexport type { LayoutOptions, NodeRadiusResolver } from './layout.js'\n\nexport { computeGraphBounds, fitGraphToViewport } from './bounds.js'\nexport type { FitOptions } from './bounds.js'\n\nexport {\n buildAdjacency,\n neighborsOf,\n expandNeighborhood,\n subgraphForNodes,\n neighborhoodSubgraph,\n} from './neighborhood.js'\nexport type { ExpandOptions } from './neighborhood.js'\n\nexport {\n GRAPH_SNAPSHOT_VERSION,\n serializeGraphSnapshot,\n stringifyGraphSnapshot,\n deserializeGraphSnapshot,\n} from './serialize.js'\nexport type { GraphSnapshot, SerializeSnapshotOptions } from './serialize.js'\n\nexport { GraphController } from './controller.js'\nexport type {\n GraphControllerDb,\n GraphSourceMode,\n GraphLayoutState,\n GraphTransitionState,\n GraphControllerStatus,\n GraphSourceRef,\n GraphControllerState,\n GraphControllerOptions,\n GraphControllerListener,\n} from './controller.js'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fortemi/graph",
3
- "version": "2026.6.8",
3
+ "version": "2026.7.0",
4
4
  "description": "Framework-agnostic graph tooling for fortemi: pure TypeScript helpers for community-graph layout, filtering, color assignment, degree sizing, bounds/fit, neighborhood expansion, and static snapshot serialization, plus a framework-agnostic GraphController for graph-source selection. No React. Depends on @fortemi/core; consumed by @fortemi/react and JS-only hosts.",
5
5
  "keywords": [
6
6
  "fortemi",
@@ -43,7 +43,7 @@
43
43
  "LICENSE"
44
44
  ],
45
45
  "dependencies": {
46
- "@fortemi/core": "2026.6.8"
46
+ "@fortemi/core": "2026.7.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "tsup": "^8.3.5",