@memberjunction/ng-clustering 5.39.0 → 5.40.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.
Files changed (49) hide show
  1. package/dist/__tests__/cluster-view-type.test.d.ts +2 -0
  2. package/dist/__tests__/cluster-view-type.test.d.ts.map +1 -0
  3. package/dist/__tests__/cluster-view-type.test.js +63 -0
  4. package/dist/__tests__/cluster-view-type.test.js.map +1 -0
  5. package/dist/lib/cluster-config-panel.component.d.ts +39 -1
  6. package/dist/lib/cluster-config-panel.component.d.ts.map +1 -1
  7. package/dist/lib/cluster-config-panel.component.js +287 -88
  8. package/dist/lib/cluster-config-panel.component.js.map +1 -1
  9. package/dist/lib/cluster-scatter.component.d.ts +53 -1
  10. package/dist/lib/cluster-scatter.component.d.ts.map +1 -1
  11. package/dist/lib/cluster-scatter.component.js +385 -135
  12. package/dist/lib/cluster-scatter.component.js.map +1 -1
  13. package/dist/lib/clustering.module.d.ts +7 -4
  14. package/dist/lib/clustering.module.d.ts.map +1 -1
  15. package/dist/lib/clustering.module.js +20 -3
  16. package/dist/lib/clustering.module.js.map +1 -1
  17. package/dist/lib/clustering.service.d.ts +63 -3
  18. package/dist/lib/clustering.service.d.ts.map +1 -1
  19. package/dist/lib/clustering.service.js +169 -48
  20. package/dist/lib/clustering.service.js.map +1 -1
  21. package/dist/lib/clustering.types.d.ts +17 -2
  22. package/dist/lib/clustering.types.d.ts.map +1 -1
  23. package/dist/lib/clustering.types.js +3 -0
  24. package/dist/lib/clustering.types.js.map +1 -1
  25. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +31 -0
  26. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -0
  27. package/dist/lib/view-type/cluster-view-prop-sheet.component.js +185 -0
  28. package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -0
  29. package/dist/lib/view-type/cluster-view-renderer.component.d.ts +143 -0
  30. package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -0
  31. package/dist/lib/view-type/cluster-view-renderer.component.js +508 -0
  32. package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -0
  33. package/dist/lib/view-type/cluster-view-type.d.ts +35 -0
  34. package/dist/lib/view-type/cluster-view-type.d.ts.map +1 -0
  35. package/dist/lib/view-type/cluster-view-type.js +51 -0
  36. package/dist/lib/view-type/cluster-view-type.js.map +1 -0
  37. package/dist/lib/view-type/cluster-view.types.d.ts +31 -0
  38. package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -0
  39. package/dist/lib/view-type/cluster-view.types.js +29 -0
  40. package/dist/lib/view-type/cluster-view.types.js.map +1 -0
  41. package/dist/lib/view-type/entity-document-availability.engine.d.ts +80 -0
  42. package/dist/lib/view-type/entity-document-availability.engine.d.ts.map +1 -0
  43. package/dist/lib/view-type/entity-document-availability.engine.js +149 -0
  44. package/dist/lib/view-type/entity-document-availability.engine.js.map +1 -0
  45. package/dist/public-api.d.ts +5 -0
  46. package/dist/public-api.d.ts.map +1 -1
  47. package/dist/public-api.js +6 -0
  48. package/dist/public-api.js.map +1 -1
  49. package/package.json +11 -3
@@ -1,24 +1,71 @@
1
1
  /**
2
- * @fileoverview Pure clustering + dimensionality-reduction service.
2
+ * @fileoverview Clustering + dimensionality-reduction orchestration service.
3
3
  *
4
- * This service is GENERIC — it accepts pre-fetched vectors and returns
5
- * clustering results. It does NOT fetch data from any database or API.
4
+ * This service has two complementary paths:
6
5
  *
7
- * Pipeline:
8
- * 1. Accept vectors from the caller
9
- * 2. Run clustering via SimpleVectorService (K-Means or DBSCAN)
10
- * 3. Reduce dimensions to 2D via UMAP (or PCA fallback)
11
- * 4. Return ClusterVisualizationResult
6
+ * 1. **Server-delegated** ({@link ClusteringService.RunClusterAnalysis}) —
7
+ * the preferred path when a data provider is available. The heavy
8
+ * fetch → cluster → reduce → name pipeline runs server-side via the
9
+ * `RunClusterAnalysis` GraphQL mutation (engine: `@memberjunction/clustering-engine`).
10
+ * The transport is the typed {@link GraphQLClusterClient}. This keeps large
11
+ * vector payloads off the wire and lets the server own LLM cluster-naming.
12
+ *
13
+ * 2. **In-browser fallback** ({@link ClusteringService.RunClustering}) — accepts
14
+ * pre-fetched vectors and runs the clustering + 2D projection locally via
15
+ * `SimpleVectorService` + UMAP/PCA. Used when no provider/endpoint is
16
+ * available, or by callers that already hold the vectors.
17
+ *
18
+ * The public API of the in-browser path is unchanged so existing consumers keep
19
+ * compiling.
12
20
  */
13
21
  import { Injectable } from '@angular/core';
14
22
  import { SimpleVectorService } from '@memberjunction/ai-vectors-memory';
23
+ import { Metadata } from '@memberjunction/core';
24
+ import { GraphQLDataProvider, GraphQLClusterClient, } from '@memberjunction/graphql-dataprovider';
15
25
  import { CLUSTER_COLORS, } from './clustering.types';
16
26
  import * as i0 from "@angular/core";
17
27
  export class ClusteringService {
18
28
  /**
19
- * Run the full clustering pipeline on pre-fetched vectors:
29
+ * Run the full clustering pipeline on the **server** via the
30
+ * `RunClusterAnalysis` GraphQL mutation, then map the result into the
31
+ * Angular {@link ClusterVisualizationResult} shape used for rendering.
32
+ *
33
+ * This is the preferred path: the server fetches the vectors, clusters,
34
+ * reduces dimensions, and (optionally) names clusters with an LLM — so the
35
+ * client never has to ship large vector payloads or duplicate that logic.
36
+ *
37
+ * The data provider is resolved per multi-provider rules: the caller may
38
+ * pass an explicit {@link IMetadataProvider}; otherwise the global
39
+ * `Metadata.Provider` is used. The provider must be a {@link GraphQLDataProvider}
40
+ * (the browser transport); when it is not, this method returns `null` so the
41
+ * caller can fall back to {@link ClusteringService.RunClustering}.
42
+ *
43
+ * @param config Clustering configuration (algorithm, K, epsilon, etc.).
44
+ * @param options Optional server-only knobs (NameClusters, Dimensions, PersistName, EntityID).
45
+ * @param provider Optional metadata provider to scope the request to a specific server.
46
+ * @returns A visualization result, or `null` when no GraphQL provider is available.
47
+ */
48
+ async RunClusterAnalysis(config, options, provider) {
49
+ const gqlProvider = this.resolveGraphQLProvider(provider);
50
+ if (!gqlProvider) {
51
+ // No GraphQL endpoint available — caller should fall back to RunClustering().
52
+ return null;
53
+ }
54
+ const client = new GraphQLClusterClient(gqlProvider);
55
+ const input = this.buildAnalysisInput(config, options);
56
+ const serverResult = await client.RunClusterAnalysis(input);
57
+ if (!serverResult.Success) {
58
+ throw new Error(serverResult.ErrorMessage || 'Cluster analysis failed on the server.');
59
+ }
60
+ return this.mapServerResult(serverResult, config);
61
+ }
62
+ /**
63
+ * Run the full clustering pipeline on pre-fetched vectors **in the browser**:
20
64
  * cluster, project to 2D, and return a visualization result.
21
65
  *
66
+ * This is the fallback path used when no GraphQL provider is available, or
67
+ * by callers that already hold the vectors and want local computation.
68
+ *
22
69
  * @param vectors Array of vectors with keys, raw number arrays, and optional metadata.
23
70
  * @param config Clustering configuration (algorithm, K, epsilon, etc.).
24
71
  * @returns A complete visualization result ready for rendering.
@@ -44,12 +91,12 @@ export class ClusteringService {
44
91
  const k = Math.min(config.K, vectors.length);
45
92
  clusterResult = svc.KMeansCluster(k, 100, config.DistanceMetric);
46
93
  }
47
- // Step 2: Reduce dimensions to 2D
94
+ // Step 2: Reduce dimensions to 2D or 3D
48
95
  const vectorMap = new Map();
49
96
  for (const v of vectors) {
50
97
  vectorMap.set(v.Key, v.Vector);
51
98
  }
52
- const projected = await this.reduceDimensionsKeyed(vectorMap);
99
+ const projected = await this.reduceDimensionsKeyed(vectorMap, config.Dimensions ?? 2);
53
100
  // Step 3: Build result
54
101
  return this.buildResult(vectors, clusterResult, projected, config, startTime);
55
102
  }
@@ -77,66 +124,141 @@ export class ClusteringService {
77
124
  });
78
125
  }
79
126
  // ================================================================
80
- // Private helpers
127
+ // Private helpers — server delegation
128
+ // ================================================================
129
+ /**
130
+ * Resolve a {@link GraphQLDataProvider} from the supplied provider (or the
131
+ * global default). Returns `null` when the active provider is not a GraphQL
132
+ * provider (e.g. a non-browser transport), signalling the caller to fall
133
+ * back to the in-browser pipeline.
134
+ */
135
+ resolveGraphQLProvider(provider) {
136
+ const p = provider ?? Metadata.Provider;
137
+ return p instanceof GraphQLDataProvider ? p : null;
138
+ }
139
+ /** Build the {@link RunClusterAnalysisInput} from the config + server options. */
140
+ buildAnalysisInput(config, options) {
141
+ return {
142
+ EntityName: config.EntityName,
143
+ EntityID: options?.EntityID,
144
+ EntityDocumentID: config.EntityDocumentID,
145
+ EntityDocumentIDs: config.EntityDocumentIDs,
146
+ ColorBy: config.ColorBy,
147
+ Algorithm: config.Algorithm,
148
+ K: config.K,
149
+ Epsilon: config.Epsilon,
150
+ MinPoints: config.MinPoints,
151
+ DistanceMetric: config.DistanceMetric,
152
+ MaxRecords: config.MaxRecords,
153
+ Filter: config.Filter,
154
+ NameClusters: options?.NameClusters,
155
+ Dimensions: config.Dimensions ?? options?.Dimensions,
156
+ PersistName: options?.PersistName,
157
+ };
158
+ }
159
+ /**
160
+ * Map the server's {@link RunClusterAnalysisResult} (engine-shaped, with
161
+ * `ClusterIndex` / `Index` / `Key`) into the Angular
162
+ * {@link ClusterVisualizationResult} shape (`ClusterId` / `Id` / `VectorKey`)
163
+ * consumed by the scatter plot.
164
+ */
165
+ mapServerResult(serverResult, config) {
166
+ const points = serverResult.Points.map(p => this.mapServerPoint(p));
167
+ const clusters = serverResult.Clusters.map(c => this.mapServerCluster(c));
168
+ clusters.sort((a, b) => a.Id - b.Id);
169
+ const m = serverResult.Metrics;
170
+ const metrics = {
171
+ SilhouetteScore: m.SilhouetteScore,
172
+ ClusterCount: m.ClusterCount,
173
+ ComputationTimeMs: m.ComputationTimeMs,
174
+ RecordCount: m.RecordCount,
175
+ OutlierCount: m.OutlierCount,
176
+ };
177
+ return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };
178
+ }
179
+ /** Map a single engine-shaped point into the Angular {@link ClusterPoint} shape. */
180
+ mapServerPoint(p) {
181
+ return {
182
+ X: p.X,
183
+ Y: p.Y,
184
+ Z: p.Z,
185
+ ClusterId: p.ClusterIndex,
186
+ Label: p.Label ?? p.Key,
187
+ VectorKey: p.Key,
188
+ Metadata: p.Metadata ?? {},
189
+ };
190
+ }
191
+ /** Map a single engine-shaped cluster into the Angular {@link ClusterInfo} shape. */
192
+ mapServerCluster(c) {
193
+ return {
194
+ Id: c.Index,
195
+ Label: c.Label,
196
+ Color: c.Color || CLUSTER_COLORS[c.Index % CLUSTER_COLORS.length],
197
+ MemberCount: c.MemberCount,
198
+ };
199
+ }
200
+ // ================================================================
201
+ // Private helpers — in-browser pipeline
81
202
  // ================================================================
82
203
  /**
83
204
  * Reduce high-dimensional vectors to 2D using UMAP.
84
205
  * Falls back to PCA if UMAP is not available.
85
206
  */
86
- async reduceDimensionsKeyed(vectorMap) {
207
+ async reduceDimensionsKeyed(vectorMap, dimensions = 2) {
87
208
  const keys = Array.from(vectorMap.keys());
88
209
  const vectors = keys.map(k => vectorMap.get(k));
89
210
  const result = new Map();
90
211
  if (vectors.length <= 2) {
91
212
  // Trivial case
92
213
  keys.forEach((key, i) => {
93
- result.set(key, [i * 500 + 250, 350]);
214
+ result.set(key, dimensions === 3 ? [i * 500 + 250, 350, 350] : [i * 500 + 250, 350]);
94
215
  });
95
216
  return result;
96
217
  }
97
218
  try {
98
219
  const { UMAP } = await import('umap-js');
99
220
  const umap = new UMAP({
100
- nComponents: 2,
221
+ nComponents: dimensions,
101
222
  nNeighbors: Math.min(15, Math.max(2, Math.floor(vectors.length / 5))),
102
223
  minDist: 0.1,
103
224
  spread: 1.0,
104
225
  });
105
226
  const embedding = umap.fit(vectors);
106
- this.normalizeAndAssign(keys, embedding, result);
227
+ this.normalizeAndAssign(keys, embedding, result, dimensions);
107
228
  }
108
229
  catch {
109
- // PCA fallback: project onto first 2 principal components
110
- this.pcaFallback(keys, vectors, result);
230
+ // PCA fallback: project onto first N principal components
231
+ this.pcaFallback(keys, vectors, result, dimensions);
111
232
  }
112
233
  return result;
113
234
  }
114
- /** Normalize 2D embedding to [padding, viewBoxSize - padding] range */
115
- normalizeAndAssign(keys, embedding, result) {
235
+ /** Normalize an N-dimensional embedding to the [padding, size - padding] range per axis. */
236
+ normalizeAndAssign(keys, embedding, result, dimensions = 2) {
116
237
  const padding = 60;
117
238
  const size = 1000;
118
239
  const usable = size - 2 * padding;
119
- let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
120
- for (const [x, y] of embedding) {
121
- if (x < minX)
122
- minX = x;
123
- if (x > maxX)
124
- maxX = x;
125
- if (y < minY)
126
- minY = y;
127
- if (y > maxY)
128
- maxY = y;
240
+ const mins = new Array(dimensions).fill(Infinity);
241
+ const maxs = new Array(dimensions).fill(-Infinity);
242
+ for (const row of embedding) {
243
+ for (let d = 0; d < dimensions; d++) {
244
+ const val = row[d] ?? 0;
245
+ if (val < mins[d])
246
+ mins[d] = val;
247
+ if (val > maxs[d])
248
+ maxs[d] = val;
249
+ }
129
250
  }
130
- const rangeX = maxX - minX || 1;
131
- const rangeY = maxY - minY || 1;
251
+ const ranges = mins.map((min, d) => (maxs[d] - min) || 1);
132
252
  keys.forEach((key, i) => {
133
- const nx = padding + ((embedding[i][0] - minX) / rangeX) * usable;
134
- const ny = padding + ((embedding[i][1] - minY) / rangeY) * usable;
135
- result.set(key, [nx, ny]);
253
+ const coord = [];
254
+ for (let d = 0; d < dimensions; d++) {
255
+ coord.push(padding + (((embedding[i][d] ?? 0) - mins[d]) / ranges[d]) * usable);
256
+ }
257
+ result.set(key, coord);
136
258
  });
137
259
  }
138
- /** Simple PCA: compute mean, subtract, take first 2 components via power iteration */
139
- pcaFallback(keys, vectors, result) {
260
+ /** Simple PCA: compute mean, subtract, take first N components via power iteration. */
261
+ pcaFallback(keys, vectors, result, dimensions = 2) {
140
262
  const dim = vectors[0].length;
141
263
  const n = vectors.length;
142
264
  // Compute mean
@@ -149,9 +271,9 @@ export class ClusteringService {
149
271
  mean[d] /= n;
150
272
  // Center data
151
273
  const centered = vectors.map(v => v.map((val, d) => val - mean[d]));
152
- // Power iteration for first 2 components
274
+ // Power iteration for first N components
153
275
  const components = [];
154
- for (let comp = 0; comp < 2; comp++) {
276
+ for (let comp = 0; comp < dimensions; comp++) {
155
277
  let w = new Array(dim).fill(0).map(() => Math.random() - 0.5);
156
278
  for (let iter = 0; iter < 50; iter++) {
157
279
  const newW = new Array(dim).fill(0);
@@ -172,12 +294,9 @@ export class ClusteringService {
172
294
  }
173
295
  components.push(w);
174
296
  }
175
- // Project
176
- const projections = centered.map(row => [
177
- row.reduce((s, val, d) => s + val * components[0][d], 0),
178
- row.reduce((s, val, d) => s + val * (components[1]?.[d] ?? 0), 0),
179
- ]);
180
- this.normalizeAndAssign(keys, projections, result);
297
+ // Project onto each component
298
+ const projections = centered.map(row => components.map(comp => row.reduce((s, val, d) => s + val * comp[d], 0)));
299
+ this.normalizeAndAssign(keys, projections, result, dimensions);
181
300
  }
182
301
  /** Build a ClusterVisualizationResult from raw data */
183
302
  buildResult(vectors, clusterResult, projected, config, startTime) {
@@ -195,11 +314,13 @@ export class ClusteringService {
195
314
  }
196
315
  }
197
316
  // Build points
317
+ const is3D = (config.Dimensions ?? 2) === 3;
198
318
  const points = vectors.map(v => {
199
- const [x, y] = projected.get(v.Key) ?? [500, 350];
319
+ const coord = projected.get(v.Key) ?? [500, 350, 350];
200
320
  return {
201
- X: x,
202
- Y: y,
321
+ X: coord[0] ?? 500,
322
+ Y: coord[1] ?? 350,
323
+ Z: is3D ? (coord[2] ?? 350) : undefined,
203
324
  ClusterId: keyToCluster.get(v.Key) ?? -1,
204
325
  Label: v.Label ?? v.Key,
205
326
  VectorKey: v.Key,
@@ -1 +1 @@
1
- {"version":3,"file":"clustering.service.js","sourceRoot":"","sources":["../../src/lib/clustering.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure clustering + dimensionality-reduction service.\n *\n * This service is GENERIC — it accepts pre-fetched vectors and returns\n * clustering results. It does NOT fetch data from any database or API.\n *\n * Pipeline:\n * 1. Accept vectors from the caller\n * 2. Run clustering via SimpleVectorService (K-Means or DBSCAN)\n * 3. Reduce dimensions to 2D via UMAP (or PCA fallback)\n * 4. Return ClusterVisualizationResult\n */\n\nimport { Injectable } from '@angular/core';\nimport { SimpleVectorService, VectorEntry, ClusterResult } from '@memberjunction/ai-vectors-memory';\nimport {\n ClusterConfig,\n ClusterVisualizationResult,\n ClusterPoint,\n ClusterInfo,\n ClusterMetrics,\n ClusterInputVector,\n CLUSTER_COLORS,\n} from './clustering.types';\n\n@Injectable({ providedIn: 'root' })\nexport class ClusteringService {\n\n /**\n * Run the full clustering pipeline on pre-fetched vectors:\n * cluster, project to 2D, and return a visualization result.\n *\n * @param vectors Array of vectors with keys, raw number arrays, and optional metadata.\n * @param config Clustering configuration (algorithm, K, epsilon, etc.).\n * @returns A complete visualization result ready for rendering.\n */\n public async RunClustering(\n vectors: ClusterInputVector[],\n config: ClusterConfig,\n ): Promise<ClusterVisualizationResult> {\n const startTime = performance.now();\n\n if (vectors.length === 0) {\n return this.buildEmptyResult(config, performance.now() - startTime);\n }\n\n // Step 1: Load into SimpleVectorService and run clustering\n const svc = new SimpleVectorService();\n const entries: VectorEntry[] = vectors.map(v => ({\n key: v.Key,\n vector: v.Vector,\n metadata: v.Metadata ?? {},\n }));\n svc.LoadVectors(entries);\n\n let clusterResult: ClusterResult;\n if (config.Algorithm === 'dbscan') {\n clusterResult = svc.DBSCANCluster(config.Epsilon, config.MinPoints, config.DistanceMetric);\n } else {\n const k = Math.min(config.K, vectors.length);\n clusterResult = svc.KMeansCluster(k, 100, config.DistanceMetric);\n }\n\n // Step 2: Reduce dimensions to 2D\n const vectorMap = new Map<string, number[]>();\n for (const v of vectors) {\n vectorMap.set(v.Key, v.Vector);\n }\n const projected = await this.reduceDimensionsKeyed(vectorMap);\n\n // Step 3: Build result\n return this.buildResult(vectors, clusterResult, projected, config, startTime);\n }\n\n /**\n * Reduce an array of high-dimensional vectors to 2D points using UMAP\n * (falls back to PCA if UMAP is unavailable).\n *\n * This is a standalone helper when you only need dimensionality reduction\n * without clustering.\n *\n * @param vectors Array of raw number arrays (each the same length).\n * @returns Array of {x, y} points in the same order as input.\n */\n public async ReduceDimensions(vectors: number[][]): Promise<Array<{ x: number; y: number }>> {\n if (vectors.length === 0) return [];\n\n // Build temporary keyed map\n const keys = vectors.map((_, i) => String(i));\n const keyedMap = new Map<string, number[]>();\n keys.forEach((k, i) => keyedMap.set(k, vectors[i]));\n\n const projected = await this.reduceDimensionsKeyed(keyedMap);\n return keys.map(k => {\n const [x, y] = projected.get(k) ?? [0, 0];\n return { x, y };\n });\n }\n\n // ================================================================\n // Private helpers\n // ================================================================\n\n /**\n * Reduce high-dimensional vectors to 2D using UMAP.\n * Falls back to PCA if UMAP is not available.\n */\n private async reduceDimensionsKeyed(vectorMap: Map<string, number[]>): Promise<Map<string, [number, number]>> {\n const keys = Array.from(vectorMap.keys());\n const vectors = keys.map(k => vectorMap.get(k)!);\n const result = new Map<string, [number, number]>();\n\n if (vectors.length <= 2) {\n // Trivial case\n keys.forEach((key, i) => {\n result.set(key, [i * 500 + 250, 350]);\n });\n return result;\n }\n\n try {\n const { UMAP } = await import('umap-js');\n const umap = new UMAP({\n nComponents: 2,\n nNeighbors: Math.min(15, Math.max(2, Math.floor(vectors.length / 5))),\n minDist: 0.1,\n spread: 1.0,\n });\n const embedding = umap.fit(vectors);\n this.normalizeAndAssign(keys, embedding, result);\n } catch {\n // PCA fallback: project onto first 2 principal components\n this.pcaFallback(keys, vectors, result);\n }\n\n return result;\n }\n\n /** Normalize 2D embedding to [padding, viewBoxSize - padding] range */\n private normalizeAndAssign(\n keys: string[],\n embedding: number[][],\n result: Map<string, [number, number]>\n ): void {\n const padding = 60;\n const size = 1000;\n const usable = size - 2 * padding;\n\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n for (const [x, y] of embedding) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n const rangeX = maxX - minX || 1;\n const rangeY = maxY - minY || 1;\n\n keys.forEach((key, i) => {\n const nx = padding + ((embedding[i][0] - minX) / rangeX) * usable;\n const ny = padding + ((embedding[i][1] - minY) / rangeY) * usable;\n result.set(key, [nx, ny]);\n });\n }\n\n /** Simple PCA: compute mean, subtract, take first 2 components via power iteration */\n private pcaFallback(\n keys: string[],\n vectors: number[][],\n result: Map<string, [number, number]>\n ): void {\n const dim = vectors[0].length;\n const n = vectors.length;\n\n // Compute mean\n const mean = new Array<number>(dim).fill(0);\n for (const v of vectors) {\n for (let d = 0; d < dim; d++) mean[d] += v[d];\n }\n for (let d = 0; d < dim; d++) mean[d] /= n;\n\n // Center data\n const centered = vectors.map(v => v.map((val, d) => val - mean[d]));\n\n // Power iteration for first 2 components\n const components: number[][] = [];\n for (let comp = 0; comp < 2; comp++) {\n let w = new Array<number>(dim).fill(0).map(() => Math.random() - 0.5);\n for (let iter = 0; iter < 50; iter++) {\n const newW = new Array<number>(dim).fill(0);\n for (const row of centered) {\n const dot = row.reduce((s, val, d) => s + val * w[d], 0);\n for (let d = 0; d < dim; d++) newW[d] += dot * row[d];\n }\n // Deflate by previous components\n for (const prev of components) {\n const proj = newW.reduce((s, val, d) => s + val * prev[d], 0);\n for (let d = 0; d < dim; d++) newW[d] -= proj * prev[d];\n }\n // Normalize\n const norm = Math.sqrt(newW.reduce((s, val) => s + val * val, 0)) || 1;\n w = newW.map(val => val / norm);\n }\n components.push(w);\n }\n\n // Project\n const projections = centered.map(row => [\n row.reduce((s, val, d) => s + val * components[0][d], 0),\n row.reduce((s, val, d) => s + val * (components[1]?.[d] ?? 0), 0),\n ]);\n\n this.normalizeAndAssign(keys, projections, result);\n }\n\n /** Build a ClusterVisualizationResult from raw data */\n private buildResult(\n vectors: ClusterInputVector[],\n clusterResult: ClusterResult,\n projected: Map<string, [number, number]>,\n config: ClusterConfig,\n startTime: number,\n ): ClusterVisualizationResult {\n // Build lookup: vectorKey -> clusterId\n const keyToCluster = new Map<string, number>();\n clusterResult.clusters.forEach((members, clusterId) => {\n for (const key of members) {\n keyToCluster.set(key, clusterId);\n }\n });\n\n // Mark outliers as cluster -1\n if (clusterResult.outliers) {\n for (const key of clusterResult.outliers) {\n keyToCluster.set(key, -1);\n }\n }\n\n // Build points\n const points: ClusterPoint[] = vectors.map(v => {\n const [x, y] = projected.get(v.Key) ?? [500, 350];\n return {\n X: x,\n Y: y,\n ClusterId: keyToCluster.get(v.Key) ?? -1,\n Label: v.Label ?? v.Key,\n VectorKey: v.Key,\n Metadata: v.Metadata ?? {},\n };\n });\n\n // Build cluster infos\n const clusters: ClusterInfo[] = [];\n clusterResult.clusters.forEach((members, clusterId) => {\n clusters.push({\n Id: clusterId,\n Label: `Cluster ${clusterId + 1}`,\n Color: CLUSTER_COLORS[clusterId % CLUSTER_COLORS.length],\n MemberCount: members.length,\n });\n });\n // Sort by ID\n clusters.sort((a, b) => a.Id - b.Id);\n\n const outlierCount = clusterResult.outliers?.length ?? 0;\n\n const metrics: ClusterMetrics = {\n SilhouetteScore: clusterResult.metadata?.silhouetteScore ?? 0,\n ClusterCount: clusters.length,\n ComputationTimeMs: Math.round(performance.now() - startTime),\n RecordCount: vectors.length,\n OutlierCount: outlierCount,\n };\n\n return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };\n }\n\n /** Build an empty result when no vectors are found */\n private buildEmptyResult(config: ClusterConfig, elapsedMs: number): ClusterVisualizationResult {\n return {\n Points: [],\n Clusters: [],\n Metrics: {\n SilhouetteScore: 0,\n ClusterCount: 0,\n ComputationTimeMs: Math.round(elapsedMs),\n RecordCount: 0,\n OutlierCount: 0,\n },\n Config: config,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"clustering.service.js","sourceRoot":"","sources":["../../src/lib/clustering.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Clustering + dimensionality-reduction orchestration service.\n *\n * This service has two complementary paths:\n *\n * 1. **Server-delegated** ({@link ClusteringService.RunClusterAnalysis}) —\n * the preferred path when a data provider is available. The heavy\n * fetch → cluster → reduce → name pipeline runs server-side via the\n * `RunClusterAnalysis` GraphQL mutation (engine: `@memberjunction/clustering-engine`).\n * The transport is the typed {@link GraphQLClusterClient}. This keeps large\n * vector payloads off the wire and lets the server own LLM cluster-naming.\n *\n * 2. **In-browser fallback** ({@link ClusteringService.RunClustering}) — accepts\n * pre-fetched vectors and runs the clustering + 2D projection locally via\n * `SimpleVectorService` + UMAP/PCA. Used when no provider/endpoint is\n * available, or by callers that already hold the vectors.\n *\n * The public API of the in-browser path is unchanged so existing consumers keep\n * compiling.\n */\n\nimport { Injectable } from '@angular/core';\nimport { SimpleVectorService, VectorEntry, ClusterResult } from '@memberjunction/ai-vectors-memory';\nimport { IMetadataProvider, Metadata } from '@memberjunction/core';\nimport {\n GraphQLDataProvider,\n GraphQLClusterClient,\n RunClusterAnalysisInput,\n RunClusterAnalysisResult,\n ClusterAnalysisPoint,\n ClusterAnalysisInfo,\n} from '@memberjunction/graphql-dataprovider';\nimport {\n ClusterConfig,\n ClusterVisualizationResult,\n ClusterPoint,\n ClusterInfo,\n ClusterMetrics,\n ClusterInputVector,\n CLUSTER_COLORS,\n} from './clustering.types';\n\n/**\n * Optional server-only knobs that `ClusterConfig` (the in-browser config shape)\n * does not carry, but the server pipeline supports. Passed alongside the config\n * to {@link ClusteringService.RunClusterAnalysis}.\n */\nexport interface ClusterAnalysisServerOptions {\n /** Entity ID — persisted to the analysis row when available. */\n EntityID?: string;\n /** When true, the server runs the optional LLM cluster-naming step. */\n NameClusters?: boolean;\n /** Number of dimensions for the projected layout (2 or 3). Defaults to 2. */\n Dimensions?: 2 | 3;\n /** When supplied, the server persists the analysis with this Name and returns its AnalysisID. */\n PersistName?: string;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ClusteringService {\n\n /**\n * Run the full clustering pipeline on the **server** via the\n * `RunClusterAnalysis` GraphQL mutation, then map the result into the\n * Angular {@link ClusterVisualizationResult} shape used for rendering.\n *\n * This is the preferred path: the server fetches the vectors, clusters,\n * reduces dimensions, and (optionally) names clusters with an LLM — so the\n * client never has to ship large vector payloads or duplicate that logic.\n *\n * The data provider is resolved per multi-provider rules: the caller may\n * pass an explicit {@link IMetadataProvider}; otherwise the global\n * `Metadata.Provider` is used. The provider must be a {@link GraphQLDataProvider}\n * (the browser transport); when it is not, this method returns `null` so the\n * caller can fall back to {@link ClusteringService.RunClustering}.\n *\n * @param config Clustering configuration (algorithm, K, epsilon, etc.).\n * @param options Optional server-only knobs (NameClusters, Dimensions, PersistName, EntityID).\n * @param provider Optional metadata provider to scope the request to a specific server.\n * @returns A visualization result, or `null` when no GraphQL provider is available.\n */\n public async RunClusterAnalysis(\n config: ClusterConfig,\n options?: ClusterAnalysisServerOptions,\n provider?: IMetadataProvider | null,\n ): Promise<ClusterVisualizationResult | null> {\n const gqlProvider = this.resolveGraphQLProvider(provider);\n if (!gqlProvider) {\n // No GraphQL endpoint available — caller should fall back to RunClustering().\n return null;\n }\n\n const client = new GraphQLClusterClient(gqlProvider);\n const input = this.buildAnalysisInput(config, options);\n const serverResult = await client.RunClusterAnalysis(input);\n\n if (!serverResult.Success) {\n throw new Error(serverResult.ErrorMessage || 'Cluster analysis failed on the server.');\n }\n\n return this.mapServerResult(serverResult, config);\n }\n\n /**\n * Run the full clustering pipeline on pre-fetched vectors **in the browser**:\n * cluster, project to 2D, and return a visualization result.\n *\n * This is the fallback path used when no GraphQL provider is available, or\n * by callers that already hold the vectors and want local computation.\n *\n * @param vectors Array of vectors with keys, raw number arrays, and optional metadata.\n * @param config Clustering configuration (algorithm, K, epsilon, etc.).\n * @returns A complete visualization result ready for rendering.\n */\n public async RunClustering(\n vectors: ClusterInputVector[],\n config: ClusterConfig,\n ): Promise<ClusterVisualizationResult> {\n const startTime = performance.now();\n\n if (vectors.length === 0) {\n return this.buildEmptyResult(config, performance.now() - startTime);\n }\n\n // Step 1: Load into SimpleVectorService and run clustering\n const svc = new SimpleVectorService();\n const entries: VectorEntry[] = vectors.map(v => ({\n key: v.Key,\n vector: v.Vector,\n metadata: v.Metadata ?? {},\n }));\n svc.LoadVectors(entries);\n\n let clusterResult: ClusterResult;\n if (config.Algorithm === 'dbscan') {\n clusterResult = svc.DBSCANCluster(config.Epsilon, config.MinPoints, config.DistanceMetric);\n } else {\n const k = Math.min(config.K, vectors.length);\n clusterResult = svc.KMeansCluster(k, 100, config.DistanceMetric);\n }\n\n // Step 2: Reduce dimensions to 2D or 3D\n const vectorMap = new Map<string, number[]>();\n for (const v of vectors) {\n vectorMap.set(v.Key, v.Vector);\n }\n const projected = await this.reduceDimensionsKeyed(vectorMap, config.Dimensions ?? 2);\n\n // Step 3: Build result\n return this.buildResult(vectors, clusterResult, projected, config, startTime);\n }\n\n /**\n * Reduce an array of high-dimensional vectors to 2D points using UMAP\n * (falls back to PCA if UMAP is unavailable).\n *\n * This is a standalone helper when you only need dimensionality reduction\n * without clustering.\n *\n * @param vectors Array of raw number arrays (each the same length).\n * @returns Array of {x, y} points in the same order as input.\n */\n public async ReduceDimensions(vectors: number[][]): Promise<Array<{ x: number; y: number }>> {\n if (vectors.length === 0) return [];\n\n // Build temporary keyed map\n const keys = vectors.map((_, i) => String(i));\n const keyedMap = new Map<string, number[]>();\n keys.forEach((k, i) => keyedMap.set(k, vectors[i]));\n\n const projected = await this.reduceDimensionsKeyed(keyedMap);\n return keys.map(k => {\n const [x, y] = projected.get(k) ?? [0, 0];\n return { x, y };\n });\n }\n\n // ================================================================\n // Private helpers — server delegation\n // ================================================================\n\n /**\n * Resolve a {@link GraphQLDataProvider} from the supplied provider (or the\n * global default). Returns `null` when the active provider is not a GraphQL\n * provider (e.g. a non-browser transport), signalling the caller to fall\n * back to the in-browser pipeline.\n */\n private resolveGraphQLProvider(provider?: IMetadataProvider | null): GraphQLDataProvider | null {\n const p = provider ?? Metadata.Provider;\n return p instanceof GraphQLDataProvider ? p : null;\n }\n\n /** Build the {@link RunClusterAnalysisInput} from the config + server options. */\n private buildAnalysisInput(\n config: ClusterConfig,\n options?: ClusterAnalysisServerOptions,\n ): RunClusterAnalysisInput {\n return {\n EntityName: config.EntityName,\n EntityID: options?.EntityID,\n EntityDocumentID: config.EntityDocumentID,\n EntityDocumentIDs: config.EntityDocumentIDs,\n ColorBy: config.ColorBy,\n Algorithm: config.Algorithm,\n K: config.K,\n Epsilon: config.Epsilon,\n MinPoints: config.MinPoints,\n DistanceMetric: config.DistanceMetric,\n MaxRecords: config.MaxRecords,\n Filter: config.Filter,\n NameClusters: options?.NameClusters,\n Dimensions: config.Dimensions ?? options?.Dimensions,\n PersistName: options?.PersistName,\n };\n }\n\n /**\n * Map the server's {@link RunClusterAnalysisResult} (engine-shaped, with\n * `ClusterIndex` / `Index` / `Key`) into the Angular\n * {@link ClusterVisualizationResult} shape (`ClusterId` / `Id` / `VectorKey`)\n * consumed by the scatter plot.\n */\n private mapServerResult(\n serverResult: RunClusterAnalysisResult,\n config: ClusterConfig,\n ): ClusterVisualizationResult {\n const points: ClusterPoint[] = serverResult.Points.map(p => this.mapServerPoint(p));\n const clusters: ClusterInfo[] = serverResult.Clusters.map(c => this.mapServerCluster(c));\n clusters.sort((a, b) => a.Id - b.Id);\n\n const m = serverResult.Metrics;\n const metrics: ClusterMetrics = {\n SilhouetteScore: m.SilhouetteScore,\n ClusterCount: m.ClusterCount,\n ComputationTimeMs: m.ComputationTimeMs,\n RecordCount: m.RecordCount,\n OutlierCount: m.OutlierCount,\n };\n\n return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };\n }\n\n /** Map a single engine-shaped point into the Angular {@link ClusterPoint} shape. */\n private mapServerPoint(p: ClusterAnalysisPoint): ClusterPoint {\n return {\n X: p.X,\n Y: p.Y,\n Z: p.Z,\n ClusterId: p.ClusterIndex,\n Label: p.Label ?? p.Key,\n VectorKey: p.Key,\n Metadata: p.Metadata ?? {},\n };\n }\n\n /** Map a single engine-shaped cluster into the Angular {@link ClusterInfo} shape. */\n private mapServerCluster(c: ClusterAnalysisInfo): ClusterInfo {\n return {\n Id: c.Index,\n Label: c.Label,\n Color: c.Color || CLUSTER_COLORS[c.Index % CLUSTER_COLORS.length],\n MemberCount: c.MemberCount,\n };\n }\n\n // ================================================================\n // Private helpers — in-browser pipeline\n // ================================================================\n\n /**\n * Reduce high-dimensional vectors to 2D using UMAP.\n * Falls back to PCA if UMAP is not available.\n */\n private async reduceDimensionsKeyed(\n vectorMap: Map<string, number[]>,\n dimensions: 2 | 3 = 2,\n ): Promise<Map<string, number[]>> {\n const keys = Array.from(vectorMap.keys());\n const vectors = keys.map(k => vectorMap.get(k)!);\n const result = new Map<string, number[]>();\n\n if (vectors.length <= 2) {\n // Trivial case\n keys.forEach((key, i) => {\n result.set(key, dimensions === 3 ? [i * 500 + 250, 350, 350] : [i * 500 + 250, 350]);\n });\n return result;\n }\n\n try {\n const { UMAP } = await import('umap-js');\n const umap = new UMAP({\n nComponents: dimensions,\n nNeighbors: Math.min(15, Math.max(2, Math.floor(vectors.length / 5))),\n minDist: 0.1,\n spread: 1.0,\n });\n const embedding = umap.fit(vectors);\n this.normalizeAndAssign(keys, embedding, result, dimensions);\n } catch {\n // PCA fallback: project onto first N principal components\n this.pcaFallback(keys, vectors, result, dimensions);\n }\n\n return result;\n }\n\n /** Normalize an N-dimensional embedding to the [padding, size - padding] range per axis. */\n private normalizeAndAssign(\n keys: string[],\n embedding: number[][],\n result: Map<string, number[]>,\n dimensions: 2 | 3 = 2,\n ): void {\n const padding = 60;\n const size = 1000;\n const usable = size - 2 * padding;\n\n const mins = new Array<number>(dimensions).fill(Infinity);\n const maxs = new Array<number>(dimensions).fill(-Infinity);\n for (const row of embedding) {\n for (let d = 0; d < dimensions; d++) {\n const val = row[d] ?? 0;\n if (val < mins[d]) mins[d] = val;\n if (val > maxs[d]) maxs[d] = val;\n }\n }\n const ranges = mins.map((min, d) => (maxs[d] - min) || 1);\n\n keys.forEach((key, i) => {\n const coord: number[] = [];\n for (let d = 0; d < dimensions; d++) {\n coord.push(padding + (((embedding[i][d] ?? 0) - mins[d]) / ranges[d]) * usable);\n }\n result.set(key, coord);\n });\n }\n\n /** Simple PCA: compute mean, subtract, take first N components via power iteration. */\n private pcaFallback(\n keys: string[],\n vectors: number[][],\n result: Map<string, number[]>,\n dimensions: 2 | 3 = 2,\n ): void {\n const dim = vectors[0].length;\n const n = vectors.length;\n\n // Compute mean\n const mean = new Array<number>(dim).fill(0);\n for (const v of vectors) {\n for (let d = 0; d < dim; d++) mean[d] += v[d];\n }\n for (let d = 0; d < dim; d++) mean[d] /= n;\n\n // Center data\n const centered = vectors.map(v => v.map((val, d) => val - mean[d]));\n\n // Power iteration for first N components\n const components: number[][] = [];\n for (let comp = 0; comp < dimensions; comp++) {\n let w = new Array<number>(dim).fill(0).map(() => Math.random() - 0.5);\n for (let iter = 0; iter < 50; iter++) {\n const newW = new Array<number>(dim).fill(0);\n for (const row of centered) {\n const dot = row.reduce((s, val, d) => s + val * w[d], 0);\n for (let d = 0; d < dim; d++) newW[d] += dot * row[d];\n }\n // Deflate by previous components\n for (const prev of components) {\n const proj = newW.reduce((s, val, d) => s + val * prev[d], 0);\n for (let d = 0; d < dim; d++) newW[d] -= proj * prev[d];\n }\n // Normalize\n const norm = Math.sqrt(newW.reduce((s, val) => s + val * val, 0)) || 1;\n w = newW.map(val => val / norm);\n }\n components.push(w);\n }\n\n // Project onto each component\n const projections = centered.map(row =>\n components.map(comp => row.reduce((s, val, d) => s + val * comp[d], 0)),\n );\n\n this.normalizeAndAssign(keys, projections, result, dimensions);\n }\n\n /** Build a ClusterVisualizationResult from raw data */\n private buildResult(\n vectors: ClusterInputVector[],\n clusterResult: ClusterResult,\n projected: Map<string, number[]>,\n config: ClusterConfig,\n startTime: number,\n ): ClusterVisualizationResult {\n // Build lookup: vectorKey -> clusterId\n const keyToCluster = new Map<string, number>();\n clusterResult.clusters.forEach((members, clusterId) => {\n for (const key of members) {\n keyToCluster.set(key, clusterId);\n }\n });\n\n // Mark outliers as cluster -1\n if (clusterResult.outliers) {\n for (const key of clusterResult.outliers) {\n keyToCluster.set(key, -1);\n }\n }\n\n // Build points\n const is3D = (config.Dimensions ?? 2) === 3;\n const points: ClusterPoint[] = vectors.map(v => {\n const coord = projected.get(v.Key) ?? [500, 350, 350];\n return {\n X: coord[0] ?? 500,\n Y: coord[1] ?? 350,\n Z: is3D ? (coord[2] ?? 350) : undefined,\n ClusterId: keyToCluster.get(v.Key) ?? -1,\n Label: v.Label ?? v.Key,\n VectorKey: v.Key,\n Metadata: v.Metadata ?? {},\n };\n });\n\n // Build cluster infos\n const clusters: ClusterInfo[] = [];\n clusterResult.clusters.forEach((members, clusterId) => {\n clusters.push({\n Id: clusterId,\n Label: `Cluster ${clusterId + 1}`,\n Color: CLUSTER_COLORS[clusterId % CLUSTER_COLORS.length],\n MemberCount: members.length,\n });\n });\n // Sort by ID\n clusters.sort((a, b) => a.Id - b.Id);\n\n const outlierCount = clusterResult.outliers?.length ?? 0;\n\n const metrics: ClusterMetrics = {\n SilhouetteScore: clusterResult.metadata?.silhouetteScore ?? 0,\n ClusterCount: clusters.length,\n ComputationTimeMs: Math.round(performance.now() - startTime),\n RecordCount: vectors.length,\n OutlierCount: outlierCount,\n };\n\n return { Points: points, Clusters: clusters, Metrics: metrics, Config: config };\n }\n\n /** Build an empty result when no vectors are found */\n private buildEmptyResult(config: ClusterConfig, elapsedMs: number): ClusterVisualizationResult {\n return {\n Points: [],\n Clusters: [],\n Metrics: {\n SilhouetteScore: 0,\n ClusterCount: 0,\n ComputationTimeMs: Math.round(elapsedMs),\n RecordCount: 0,\n OutlierCount: 0,\n },\n Config: config,\n };\n }\n}\n"]}
@@ -94,6 +94,8 @@ export interface ClusterConfigPanelEntityDocOption {
94
94
  ID: string;
95
95
  /** Display name of the entity document */
96
96
  Name: string;
97
+ /** Name of the entity this document belongs to (used for multi-entity selection grouping/labels). */
98
+ EntityName?: string;
97
99
  }
98
100
  /**
99
101
  * A single vector provided to `ClusteringService.RunClustering()`.
@@ -118,10 +120,12 @@ export interface ClusterInputVector {
118
120
  * dimensionality reduction (UMAP or PCA).
119
121
  */
120
122
  export interface ClusterPoint {
121
- /** X coordinate in 2D projected space. */
123
+ /** X coordinate in projected space. */
122
124
  X: number;
123
- /** Y coordinate in 2D projected space. */
125
+ /** Y coordinate in projected space. */
124
126
  Y: number;
127
+ /** Z coordinate in projected space (only present for 3D projections). */
128
+ Z?: number;
125
129
  /** Cluster assignment ID (`-1` for outliers / noise). */
126
130
  ClusterId: number;
127
131
  /** Display label for this point (typically the entity record identifier). */
@@ -162,6 +166,17 @@ export interface ClusterConfig {
162
166
  EntityName: string;
163
167
  /** Specific Entity Document ID to use. When blank, the first active doc for the entity is used. */
164
168
  EntityDocumentID: string;
169
+ /**
170
+ * Multi-entity clustering: one or more Entity Document IDs whose vectors are
171
+ * merged into a single analysis. When two or more are selected this takes
172
+ * precedence over {@link EntityDocumentID}/{@link EntityName}. All selected
173
+ * documents must use the same embedding model — the server hard-blocks mismatches.
174
+ */
175
+ EntityDocumentIDs?: string[];
176
+ /** Legend mode: color points by their assigned cluster (default) or by source entity. */
177
+ ColorBy?: 'cluster' | 'entity';
178
+ /** Projected layout dimensionality: 2 (default, SVG) or 3 (rotatable projection). */
179
+ Dimensions?: 2 | 3;
165
180
  /** Clustering algorithm to use. */
166
181
  Algorithm: ClusterAlgorithm;
167
182
  /** Number of clusters for K-Means (ignored for DBSCAN). */
@@ -1 +1 @@
1
- {"version":3,"file":"clustering.types.d.ts","sourceRoot":"","sources":["../../src/lib/clustering.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"clustering.types.d.ts","sourceRoot":"","sources":["../../src/lib/clustering.types.ts"],"names":[],"mappings":"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"}
@@ -62,6 +62,9 @@ export function DefaultClusterConfig() {
62
62
  return {
63
63
  EntityName: '',
64
64
  EntityDocumentID: '',
65
+ EntityDocumentIDs: [],
66
+ ColorBy: 'cluster',
67
+ Dimensions: 2,
65
68
  Algorithm: 'kmeans',
66
69
  K: 4,
67
70
  Epsilon: 0.3,
@@ -1 +1 @@
1
- {"version":3,"file":"clustering.types.js","sourceRoot":"","sources":["../../src/lib/clustering.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwQH,mEAAmE;AACnE,YAAY;AACZ,mEAAmE;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAa;IACpC,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,UAAU;IACrB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,MAAM;IACjB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,UAAU;CACxB,CAAC;AAEF,mEAAmE;AACnE,oBAAoB;AACpB,mEAAmE;AAEnE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB;IAChC,OAAO;QACH,UAAU,EAAE,EAAE;QACd,gBAAgB,EAAE,EAAE;QACpB,SAAS,EAAE,QAAQ;QACnB,CAAC,EAAE,CAAC;QACJ,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,EAAE;KACb,CAAC;AACN,CAAC","sourcesContent":["/**\n * @fileoverview Shared types for the clustering visualization library.\n *\n * This module defines every interface, type alias, and constant used across the\n * `@memberjunction/ng-clustering` package. It is the single source of truth for:\n *\n * - **Data shapes** (`ClusterPoint`, `ClusterInfo`, `ClusterVisualizationResult`, ...)\n * - **Configuration** (`ClusterConfig`, `ClusterAlgorithm`, `ClusterDistanceMetric`)\n * - **Event payloads** (`CancelableEvent`, `ViewportRect`, `ClusterSelectedEvent`, ...)\n * - **Constants** (`CLUSTER_COLORS`, `DefaultClusterConfig`)\n *\n * @example\n * ```typescript\n * import {\n * CancelableEvent,\n * ClusterPoint,\n * ClusterConfig,\n * DefaultClusterConfig,\n * } from '@memberjunction/ng-clustering';\n *\n * const cfg = DefaultClusterConfig();\n * cfg.EntityName = 'Companies';\n * cfg.K = 5;\n * ```\n */\n\n// ================================================================\n// Cancelable event pattern\n// ================================================================\n\n/**\n * Generic wrapper for cancelable events emitted by clustering components.\n *\n * The component emits a `CancelableEvent<T>` **before** performing a\n * side-effect. The consumer can inspect `Data`, and set `Cancel = true` to\n * prevent the default behavior.\n *\n * @typeParam T Payload type carried by the event.\n *\n * @example\n * ```html\n * <mj-cluster-scatter\n * (BeforePointClick)=\"onBeforeClick($event)\">\n * </mj-cluster-scatter>\n * ```\n * ```typescript\n * onBeforeClick(event: CancelableEvent<ClusterPoint>): void {\n * if (event.Data.ClusterId === -1) {\n * event.Cancel = true; // suppress click on outliers\n * }\n * }\n * ```\n */\nexport interface CancelableEvent<T = unknown> {\n /** The event payload. */\n Data: T;\n /** Set to `true` to cancel the default operation that would follow. */\n Cancel: boolean;\n}\n\n// ================================================================\n// Viewport / selection event payloads\n// ================================================================\n\n/**\n * Describes the visible rectangle of the scatter plot after a zoom or pan.\n *\n * Coordinates are in the SVG coordinate space (the `viewBox`).\n */\nexport interface ViewportRect {\n /** Left edge X coordinate. */\n MinX: number;\n /** Top edge Y coordinate. */\n MinY: number;\n /** Viewport width in SVG units. */\n Width: number;\n /** Viewport height in SVG units. */\n Height: number;\n}\n\n/**\n * Payload emitted when the user clicks a cluster label in the legend.\n */\nexport interface ClusterSelectedEvent {\n /** The numeric cluster ID that was selected. */\n ClusterId: number;\n /** The human-readable label shown in the legend. */\n Label: string;\n /** The display color of the cluster. */\n Color: string;\n /** Number of points belonging to this cluster. */\n MemberCount: number;\n}\n\n// ================================================================\n// Config panel types\n// ================================================================\n\n/**\n * Simple entity option used by the config panel's entity dropdown.\n * The parent component is responsible for populating this list.\n */\nexport interface ClusterConfigPanelEntityOption {\n /** Entity display name shown in the dropdown. */\n Name: string;\n}\n\n/** Option for the entity document dropdown in the config panel. */\nexport interface ClusterConfigPanelEntityDocOption {\n /** Entity Document ID */\n ID: string;\n /** Display name of the entity document */\n Name: string;\n}\n\n// ================================================================\n// Input types\n// ================================================================\n\n/**\n * A single vector provided to `ClusteringService.RunClustering()`.\n *\n * The caller is responsible for fetching/building these vectors from\n * whatever data source is appropriate (database, vector index, API, etc.).\n */\nexport interface ClusterInputVector {\n /** Unique key identifying this vector (e.g., a record ID). */\n Key: string;\n /** The raw embedding / feature vector. */\n Vector: number[];\n /** Optional display label for this point (defaults to Key if omitted). */\n Label?: string;\n /** Arbitrary metadata surfaced in tooltips and click handlers. */\n Metadata?: Record<string, unknown>;\n}\n\n// ================================================================\n// Core data types\n// ================================================================\n\n/**\n * A single point in the 2D scatter plot.\n *\n * Each point corresponds to one vectorized entity record after\n * dimensionality reduction (UMAP or PCA).\n */\nexport interface ClusterPoint {\n /** X coordinate in 2D projected space. */\n X: number;\n /** Y coordinate in 2D projected space. */\n Y: number;\n /** Cluster assignment ID (`-1` for outliers / noise). */\n ClusterId: number;\n /** Display label for this point (typically the entity record identifier). */\n Label: string;\n /** Original vector key (e.g., entity record ID). */\n VectorKey: string;\n /** Arbitrary metadata surfaced in tooltips and click handlers. */\n Metadata: Record<string, unknown>;\n}\n\n/**\n * Summary information about a single cluster.\n *\n * Produced by `ClusteringService.RunPipeline()` and consumed by the\n * scatter plot legend and various event payloads.\n */\nexport interface ClusterInfo {\n /** Unique cluster identifier (zero-based). */\n Id: number;\n /** Human-readable label (auto-generated or user-defined). */\n Label: string;\n /** Display color as a CSS hex string (e.g., `#5b8def`). */\n Color: string;\n /** Number of member points in this cluster. */\n MemberCount: number;\n}\n\n/** Supported clustering algorithms. */\nexport type ClusterAlgorithm = 'kmeans' | 'dbscan';\n\n/** Supported distance metrics for clustering. */\nexport type ClusterDistanceMetric = 'cosine' | 'euclidean' | 'dotproduct';\n\n/**\n * Full configuration for a clustering run.\n *\n * Pass an instance to `ClusteringService.RunPipeline()` or emit from the\n * config panel's `RunClustering` output.\n */\nexport interface ClusterConfig {\n /** Entity name to cluster (must have Entity Documents with vectors). */\n EntityName: string;\n /** Specific Entity Document ID to use. When blank, the first active doc for the entity is used. */\n EntityDocumentID: string;\n /** Clustering algorithm to use. */\n Algorithm: ClusterAlgorithm;\n /** Number of clusters for K-Means (ignored for DBSCAN). */\n K: number;\n /** Epsilon neighbourhood radius for DBSCAN (ignored for K-Means). */\n Epsilon: number;\n /** Minimum points to form a dense region in DBSCAN (ignored for K-Means). */\n MinPoints: number;\n /** Distance metric used when comparing vectors. */\n DistanceMetric: ClusterDistanceMetric;\n /** Maximum number of entity records to fetch for clustering. */\n MaxRecords: number;\n /** Optional SQL-style filter applied to the Entity Document Runs query. */\n Filter: string;\n}\n\n/**\n * Quality and performance metrics returned after a clustering run.\n */\nexport interface ClusterMetrics {\n /** Silhouette score ranging from -1 (poor) to 1 (excellent). */\n SilhouetteScore: number;\n /** Total number of clusters discovered. */\n ClusterCount: number;\n /** Wall-clock computation time in milliseconds. */\n ComputationTimeMs: number;\n /** Total records that were processed. */\n RecordCount: number;\n /** Number of outlier points (DBSCAN only; 0 for K-Means). */\n OutlierCount: number;\n}\n\n/**\n * Complete result of a clustering visualization pipeline.\n *\n * Returned by {@link ClusteringService.RunPipeline} and consumed by\n * both `mj-cluster-scatter` and `mj-cluster-config-panel`.\n */\nexport interface ClusterVisualizationResult {\n /** 2D projected points ready for rendering. */\n Points: ClusterPoint[];\n /** Cluster summaries with colors and counts. */\n Clusters: ClusterInfo[];\n /** Performance and quality metrics. */\n Metrics: ClusterMetrics;\n /** The config that produced this result. */\n Config: ClusterConfig;\n}\n\n/**\n * Serializable reference to a saved cluster visualization.\n *\n * Persisted via `UserInfoEngine` user settings so the user can reload\n * previous analyses.\n */\nexport interface SavedClusterVisualization {\n /** Unique identifier (UUID). */\n Id: string;\n /** User-given display name. */\n Name: string;\n /** Entity that was clustered. */\n EntityName: string;\n /** Algorithm used. */\n Algorithm: ClusterAlgorithm;\n /** Snapshot of the full config at save time. */\n Params: Partial<ClusterConfig>;\n /** ISO 8601 timestamp of when this was saved. */\n CreatedAt: string;\n /** Cached clustering result (points, clusters, metrics) — avoids re-computation on restore. */\n Result?: ClusterVisualizationResult;\n /** Viewport transform at save time (pan + zoom state). */\n Viewport?: ViewportTransform;\n /** LLM-generated (or user-edited) labels for each cluster. */\n ClusterLabels?: ClusterLabel[];\n}\n\n/** Viewport pan/zoom state for restoring scatter plot position. */\nexport interface ViewportTransform {\n TranslateX: number;\n TranslateY: number;\n Scale: number;\n}\n\n/** A human-readable label for a cluster. */\nexport interface ClusterLabel {\n /** The cluster ID this label applies to. */\n ClusterId: number;\n /** Short descriptive label (2-5 words). */\n Label: string;\n /** Whether the user has manually edited this label. */\n IsUserEdited: boolean;\n}\n\n// ================================================================\n// Constants\n// ================================================================\n\n/**\n * Default cluster color palette -- 10 distinct, accessible colors.\n *\n * Override at the component level via `ClusterScatterComponent.ColorPalette`.\n */\nexport const CLUSTER_COLORS: string[] = [\n '#5b8def', // blue\n '#34d399', // emerald\n '#fbbf24', // amber\n '#f472b6', // pink\n '#a78bfa', // violet\n '#fb923c', // orange\n '#22d3ee', // cyan\n '#f87171', // red\n '#4ade80', // green\n '#e879f9', // fuchsia\n];\n\n// ================================================================\n// Factory functions\n// ================================================================\n\n/**\n * Create a `ClusterConfig` populated with sensible defaults.\n *\n * @returns A new `ClusterConfig` with K-Means / cosine / 500 max records.\n *\n * @example\n * ```typescript\n * const cfg = DefaultClusterConfig();\n * cfg.EntityName = 'Accounts';\n * cfg.K = 6;\n * ```\n */\nexport function DefaultClusterConfig(): ClusterConfig {\n return {\n EntityName: '',\n EntityDocumentID: '',\n Algorithm: 'kmeans',\n K: 4,\n Epsilon: 0.3,\n MinPoints: 3,\n DistanceMetric: 'cosine',\n MaxRecords: 500,\n Filter: '',\n };\n}\n"]}
1
+ {"version":3,"file":"clustering.types.js","sourceRoot":"","sources":["../../src/lib/clustering.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAuRH,mEAAmE;AACnE,YAAY;AACZ,mEAAmE;AAEnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAa;IACpC,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,UAAU;IACrB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,OAAO;IAClB,SAAS,EAAE,MAAM;IACjB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,UAAU;CACxB,CAAC;AAEF,mEAAmE;AACnE,oBAAoB;AACpB,mEAAmE;AAEnE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB;IAChC,OAAO;QACH,UAAU,EAAE,EAAE;QACd,gBAAgB,EAAE,EAAE;QACpB,iBAAiB,EAAE,EAAE;QACrB,OAAO,EAAE,SAAS;QAClB,UAAU,EAAE,CAAC;QACb,SAAS,EAAE,QAAQ;QACnB,CAAC,EAAE,CAAC;QACJ,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,QAAQ;QACxB,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,EAAE;KACb,CAAC;AACN,CAAC","sourcesContent":["/**\n * @fileoverview Shared types for the clustering visualization library.\n *\n * This module defines every interface, type alias, and constant used across the\n * `@memberjunction/ng-clustering` package. It is the single source of truth for:\n *\n * - **Data shapes** (`ClusterPoint`, `ClusterInfo`, `ClusterVisualizationResult`, ...)\n * - **Configuration** (`ClusterConfig`, `ClusterAlgorithm`, `ClusterDistanceMetric`)\n * - **Event payloads** (`CancelableEvent`, `ViewportRect`, `ClusterSelectedEvent`, ...)\n * - **Constants** (`CLUSTER_COLORS`, `DefaultClusterConfig`)\n *\n * @example\n * ```typescript\n * import {\n * CancelableEvent,\n * ClusterPoint,\n * ClusterConfig,\n * DefaultClusterConfig,\n * } from '@memberjunction/ng-clustering';\n *\n * const cfg = DefaultClusterConfig();\n * cfg.EntityName = 'Companies';\n * cfg.K = 5;\n * ```\n */\n\n// ================================================================\n// Cancelable event pattern\n// ================================================================\n\n/**\n * Generic wrapper for cancelable events emitted by clustering components.\n *\n * The component emits a `CancelableEvent<T>` **before** performing a\n * side-effect. The consumer can inspect `Data`, and set `Cancel = true` to\n * prevent the default behavior.\n *\n * @typeParam T Payload type carried by the event.\n *\n * @example\n * ```html\n * <mj-cluster-scatter\n * (BeforePointClick)=\"onBeforeClick($event)\">\n * </mj-cluster-scatter>\n * ```\n * ```typescript\n * onBeforeClick(event: CancelableEvent<ClusterPoint>): void {\n * if (event.Data.ClusterId === -1) {\n * event.Cancel = true; // suppress click on outliers\n * }\n * }\n * ```\n */\nexport interface CancelableEvent<T = unknown> {\n /** The event payload. */\n Data: T;\n /** Set to `true` to cancel the default operation that would follow. */\n Cancel: boolean;\n}\n\n// ================================================================\n// Viewport / selection event payloads\n// ================================================================\n\n/**\n * Describes the visible rectangle of the scatter plot after a zoom or pan.\n *\n * Coordinates are in the SVG coordinate space (the `viewBox`).\n */\nexport interface ViewportRect {\n /** Left edge X coordinate. */\n MinX: number;\n /** Top edge Y coordinate. */\n MinY: number;\n /** Viewport width in SVG units. */\n Width: number;\n /** Viewport height in SVG units. */\n Height: number;\n}\n\n/**\n * Payload emitted when the user clicks a cluster label in the legend.\n */\nexport interface ClusterSelectedEvent {\n /** The numeric cluster ID that was selected. */\n ClusterId: number;\n /** The human-readable label shown in the legend. */\n Label: string;\n /** The display color of the cluster. */\n Color: string;\n /** Number of points belonging to this cluster. */\n MemberCount: number;\n}\n\n// ================================================================\n// Config panel types\n// ================================================================\n\n/**\n * Simple entity option used by the config panel's entity dropdown.\n * The parent component is responsible for populating this list.\n */\nexport interface ClusterConfigPanelEntityOption {\n /** Entity display name shown in the dropdown. */\n Name: string;\n}\n\n/** Option for the entity document dropdown in the config panel. */\nexport interface ClusterConfigPanelEntityDocOption {\n /** Entity Document ID */\n ID: string;\n /** Display name of the entity document */\n Name: string;\n /** Name of the entity this document belongs to (used for multi-entity selection grouping/labels). */\n EntityName?: string;\n}\n\n// ================================================================\n// Input types\n// ================================================================\n\n/**\n * A single vector provided to `ClusteringService.RunClustering()`.\n *\n * The caller is responsible for fetching/building these vectors from\n * whatever data source is appropriate (database, vector index, API, etc.).\n */\nexport interface ClusterInputVector {\n /** Unique key identifying this vector (e.g., a record ID). */\n Key: string;\n /** The raw embedding / feature vector. */\n Vector: number[];\n /** Optional display label for this point (defaults to Key if omitted). */\n Label?: string;\n /** Arbitrary metadata surfaced in tooltips and click handlers. */\n Metadata?: Record<string, unknown>;\n}\n\n// ================================================================\n// Core data types\n// ================================================================\n\n/**\n * A single point in the 2D scatter plot.\n *\n * Each point corresponds to one vectorized entity record after\n * dimensionality reduction (UMAP or PCA).\n */\nexport interface ClusterPoint {\n /** X coordinate in projected space. */\n X: number;\n /** Y coordinate in projected space. */\n Y: number;\n /** Z coordinate in projected space (only present for 3D projections). */\n Z?: number;\n /** Cluster assignment ID (`-1` for outliers / noise). */\n ClusterId: number;\n /** Display label for this point (typically the entity record identifier). */\n Label: string;\n /** Original vector key (e.g., entity record ID). */\n VectorKey: string;\n /** Arbitrary metadata surfaced in tooltips and click handlers. */\n Metadata: Record<string, unknown>;\n}\n\n/**\n * Summary information about a single cluster.\n *\n * Produced by `ClusteringService.RunPipeline()` and consumed by the\n * scatter plot legend and various event payloads.\n */\nexport interface ClusterInfo {\n /** Unique cluster identifier (zero-based). */\n Id: number;\n /** Human-readable label (auto-generated or user-defined). */\n Label: string;\n /** Display color as a CSS hex string (e.g., `#5b8def`). */\n Color: string;\n /** Number of member points in this cluster. */\n MemberCount: number;\n}\n\n/** Supported clustering algorithms. */\nexport type ClusterAlgorithm = 'kmeans' | 'dbscan';\n\n/** Supported distance metrics for clustering. */\nexport type ClusterDistanceMetric = 'cosine' | 'euclidean' | 'dotproduct';\n\n/**\n * Full configuration for a clustering run.\n *\n * Pass an instance to `ClusteringService.RunPipeline()` or emit from the\n * config panel's `RunClustering` output.\n */\nexport interface ClusterConfig {\n /** Entity name to cluster (must have Entity Documents with vectors). */\n EntityName: string;\n /** Specific Entity Document ID to use. When blank, the first active doc for the entity is used. */\n EntityDocumentID: string;\n /**\n * Multi-entity clustering: one or more Entity Document IDs whose vectors are\n * merged into a single analysis. When two or more are selected this takes\n * precedence over {@link EntityDocumentID}/{@link EntityName}. All selected\n * documents must use the same embedding model — the server hard-blocks mismatches.\n */\n EntityDocumentIDs?: string[];\n /** Legend mode: color points by their assigned cluster (default) or by source entity. */\n ColorBy?: 'cluster' | 'entity';\n /** Projected layout dimensionality: 2 (default, SVG) or 3 (rotatable projection). */\n Dimensions?: 2 | 3;\n /** Clustering algorithm to use. */\n Algorithm: ClusterAlgorithm;\n /** Number of clusters for K-Means (ignored for DBSCAN). */\n K: number;\n /** Epsilon neighbourhood radius for DBSCAN (ignored for K-Means). */\n Epsilon: number;\n /** Minimum points to form a dense region in DBSCAN (ignored for K-Means). */\n MinPoints: number;\n /** Distance metric used when comparing vectors. */\n DistanceMetric: ClusterDistanceMetric;\n /** Maximum number of entity records to fetch for clustering. */\n MaxRecords: number;\n /** Optional SQL-style filter applied to the Entity Document Runs query. */\n Filter: string;\n}\n\n/**\n * Quality and performance metrics returned after a clustering run.\n */\nexport interface ClusterMetrics {\n /** Silhouette score ranging from -1 (poor) to 1 (excellent). */\n SilhouetteScore: number;\n /** Total number of clusters discovered. */\n ClusterCount: number;\n /** Wall-clock computation time in milliseconds. */\n ComputationTimeMs: number;\n /** Total records that were processed. */\n RecordCount: number;\n /** Number of outlier points (DBSCAN only; 0 for K-Means). */\n OutlierCount: number;\n}\n\n/**\n * Complete result of a clustering visualization pipeline.\n *\n * Returned by {@link ClusteringService.RunPipeline} and consumed by\n * both `mj-cluster-scatter` and `mj-cluster-config-panel`.\n */\nexport interface ClusterVisualizationResult {\n /** 2D projected points ready for rendering. */\n Points: ClusterPoint[];\n /** Cluster summaries with colors and counts. */\n Clusters: ClusterInfo[];\n /** Performance and quality metrics. */\n Metrics: ClusterMetrics;\n /** The config that produced this result. */\n Config: ClusterConfig;\n}\n\n/**\n * Serializable reference to a saved cluster visualization.\n *\n * Persisted via `UserInfoEngine` user settings so the user can reload\n * previous analyses.\n */\nexport interface SavedClusterVisualization {\n /** Unique identifier (UUID). */\n Id: string;\n /** User-given display name. */\n Name: string;\n /** Entity that was clustered. */\n EntityName: string;\n /** Algorithm used. */\n Algorithm: ClusterAlgorithm;\n /** Snapshot of the full config at save time. */\n Params: Partial<ClusterConfig>;\n /** ISO 8601 timestamp of when this was saved. */\n CreatedAt: string;\n /** Cached clustering result (points, clusters, metrics) — avoids re-computation on restore. */\n Result?: ClusterVisualizationResult;\n /** Viewport transform at save time (pan + zoom state). */\n Viewport?: ViewportTransform;\n /** LLM-generated (or user-edited) labels for each cluster. */\n ClusterLabels?: ClusterLabel[];\n}\n\n/** Viewport pan/zoom state for restoring scatter plot position. */\nexport interface ViewportTransform {\n TranslateX: number;\n TranslateY: number;\n Scale: number;\n}\n\n/** A human-readable label for a cluster. */\nexport interface ClusterLabel {\n /** The cluster ID this label applies to. */\n ClusterId: number;\n /** Short descriptive label (2-5 words). */\n Label: string;\n /** Whether the user has manually edited this label. */\n IsUserEdited: boolean;\n}\n\n// ================================================================\n// Constants\n// ================================================================\n\n/**\n * Default cluster color palette -- 10 distinct, accessible colors.\n *\n * Override at the component level via `ClusterScatterComponent.ColorPalette`.\n */\nexport const CLUSTER_COLORS: string[] = [\n '#5b8def', // blue\n '#34d399', // emerald\n '#fbbf24', // amber\n '#f472b6', // pink\n '#a78bfa', // violet\n '#fb923c', // orange\n '#22d3ee', // cyan\n '#f87171', // red\n '#4ade80', // green\n '#e879f9', // fuchsia\n];\n\n// ================================================================\n// Factory functions\n// ================================================================\n\n/**\n * Create a `ClusterConfig` populated with sensible defaults.\n *\n * @returns A new `ClusterConfig` with K-Means / cosine / 500 max records.\n *\n * @example\n * ```typescript\n * const cfg = DefaultClusterConfig();\n * cfg.EntityName = 'Accounts';\n * cfg.K = 6;\n * ```\n */\nexport function DefaultClusterConfig(): ClusterConfig {\n return {\n EntityName: '',\n EntityDocumentID: '',\n EntityDocumentIDs: [],\n ColorBy: 'cluster',\n Dimensions: 2,\n Algorithm: 'kmeans',\n K: 4,\n Epsilon: 0.3,\n MinPoints: 3,\n DistanceMetric: 'cosine',\n MaxRecords: 500,\n Filter: '',\n };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { EventEmitter } from '@angular/core';
2
+ import { EntityInfo } from '@memberjunction/core';
3
+ import { IViewPropSheet } from '@memberjunction/ng-entity-viewer';
4
+ import { ClusterViewConfig } from './cluster-view.types';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * ClusterViewPropSheetComponent
8
+ * -----------------------------
9
+ * The configuration prop-sheet for the Cluster view type — honors {@link IViewPropSheet}.
10
+ * Lets the user pick the algorithm, cluster count, 2D/3D layout, legend coloring, record cap,
11
+ * and LLM naming. Emits the updated config (as the opaque host map) on every change so the host
12
+ * can persist it into `UserView.DisplayState.viewTypeConfigs` and re-render the cluster.
13
+ */
14
+ export declare class ClusterViewPropSheetComponent implements IViewPropSheet<Record<string, unknown>> {
15
+ entity: EntityInfo | null;
16
+ private _config;
17
+ set config(value: Record<string, unknown>);
18
+ get config(): Record<string, unknown>;
19
+ configChange: EventEmitter<Record<string, unknown>>;
20
+ /** The current parsed config (defaults applied) bound by the template. */
21
+ cfg: ClusterViewConfig;
22
+ /** Apply a partial change, re-emit the full config map to the host. */
23
+ patch(change: Partial<ClusterViewConfig>): void;
24
+ asAlgorithm(v: string): 'kmeans' | 'dbscan';
25
+ asDimensions(v: string): 2 | 3;
26
+ asColorBy(v: string): 'cluster' | 'entity';
27
+ asInt(v: string, fallback: number): number;
28
+ static ɵfac: i0.ɵɵFactoryDeclaration<ClusterViewPropSheetComponent, never>;
29
+ static ɵcmp: i0.ɵɵComponentDeclaration<ClusterViewPropSheetComponent, "mj-cluster-view-prop-sheet", never, { "entity": { "alias": "entity"; "required": false; }; "config": { "alias": "config"; "required": false; }; }, { "configChange": "configChange"; }, never, never, false, never>;
30
+ }
31
+ //# sourceMappingURL=cluster-view-prop-sheet.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cluster-view-prop-sheet.component.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-prop-sheet.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAqB,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAuB,MAAM,sBAAsB,CAAC;;AAE9E;;;;;;;GAOG;AACH,qBAyDa,6BAA8B,YAAW,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClF,MAAM,EAAE,UAAU,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,OAAO,CAA+B;IAC9C,IACI,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAGxC;IACD,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEpC;IAES,YAAY,wCAA+C;IAErE,0EAA0E;IACnE,GAAG,EAAE,iBAAiB,CAA2B;IAExD,uEAAuE;IACvE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAM/C,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ;IAG3C,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC;IAG9B,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ;IAG1C,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;yCAlC/B,6BAA6B;2CAA7B,6BAA6B;CAsCzC"}