@memberjunction/ng-clustering 0.0.1 → 5.24.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 (42) hide show
  1. package/README.md +441 -27
  2. package/dist/__tests__/clustering-service.test.d.ts +2 -0
  3. package/dist/__tests__/clustering-service.test.d.ts.map +1 -0
  4. package/dist/__tests__/clustering-service.test.js +487 -0
  5. package/dist/__tests__/clustering-service.test.js.map +1 -0
  6. package/dist/__tests__/clustering-types.test.d.ts +2 -0
  7. package/dist/__tests__/clustering-types.test.d.ts.map +1 -0
  8. package/dist/__tests__/clustering-types.test.js +254 -0
  9. package/dist/__tests__/clustering-types.test.js.map +1 -0
  10. package/dist/__tests__/index.test.d.ts +2 -0
  11. package/dist/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/__tests__/index.test.js +7 -0
  13. package/dist/__tests__/index.test.js.map +1 -0
  14. package/dist/__tests__/scatter-component-logic.test.d.ts +2 -0
  15. package/dist/__tests__/scatter-component-logic.test.d.ts.map +1 -0
  16. package/dist/__tests__/scatter-component-logic.test.js +494 -0
  17. package/dist/__tests__/scatter-component-logic.test.js.map +1 -0
  18. package/dist/lib/cluster-config-panel.component.d.ts +274 -0
  19. package/dist/lib/cluster-config-panel.component.d.ts.map +1 -0
  20. package/dist/lib/cluster-config-panel.component.js +732 -0
  21. package/dist/lib/cluster-config-panel.component.js.map +1 -0
  22. package/dist/lib/cluster-scatter.component.d.ts +572 -0
  23. package/dist/lib/cluster-scatter.component.d.ts.map +1 -0
  24. package/dist/lib/cluster-scatter.component.js +1410 -0
  25. package/dist/lib/cluster-scatter.component.js.map +1 -0
  26. package/dist/lib/clustering.module.d.ts +12 -0
  27. package/dist/lib/clustering.module.d.ts.map +1 -0
  28. package/dist/lib/clustering.module.js +38 -0
  29. package/dist/lib/clustering.module.js.map +1 -0
  30. package/dist/lib/clustering.service.d.ts +43 -0
  31. package/dist/lib/clustering.service.d.ts.map +1 -0
  32. package/dist/lib/clustering.service.js +253 -0
  33. package/dist/lib/clustering.service.js.map +1 -0
  34. package/dist/lib/clustering.types.d.ts +271 -0
  35. package/dist/lib/clustering.types.d.ts.map +1 -0
  36. package/dist/lib/clustering.types.js +74 -0
  37. package/dist/lib/clustering.types.js.map +1 -0
  38. package/dist/public-api.d.ts +6 -0
  39. package/dist/public-api.d.ts.map +1 -0
  40. package/dist/public-api.js +11 -0
  41. package/dist/public-api.js.map +1 -0
  42. package/package.json +41 -6
@@ -0,0 +1,254 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { DefaultClusterConfig, CLUSTER_COLORS, } from '../lib/clustering.types';
3
+ // ================================================================
4
+ // DefaultClusterConfig factory
5
+ // ================================================================
6
+ describe('DefaultClusterConfig', () => {
7
+ it('should return a new object on each call', () => {
8
+ const a = DefaultClusterConfig();
9
+ const b = DefaultClusterConfig();
10
+ expect(a).not.toBe(b);
11
+ expect(a).toEqual(b);
12
+ });
13
+ it('should have correct default values', () => {
14
+ const cfg = DefaultClusterConfig();
15
+ expect(cfg.EntityName).toBe('');
16
+ expect(cfg.EntityDocumentID).toBe('');
17
+ expect(cfg.Algorithm).toBe('kmeans');
18
+ expect(cfg.K).toBe(4);
19
+ expect(cfg.Epsilon).toBe(0.3);
20
+ expect(cfg.MinPoints).toBe(3);
21
+ expect(cfg.DistanceMetric).toBe('cosine');
22
+ expect(cfg.MaxRecords).toBe(500);
23
+ expect(cfg.Filter).toBe('');
24
+ });
25
+ it('should be mutable after creation', () => {
26
+ const cfg = DefaultClusterConfig();
27
+ cfg.EntityName = 'Companies';
28
+ cfg.K = 8;
29
+ cfg.Algorithm = 'dbscan';
30
+ expect(cfg.EntityName).toBe('Companies');
31
+ expect(cfg.K).toBe(8);
32
+ expect(cfg.Algorithm).toBe('dbscan');
33
+ });
34
+ });
35
+ // ================================================================
36
+ // CLUSTER_COLORS constant
37
+ // ================================================================
38
+ describe('CLUSTER_COLORS', () => {
39
+ it('should contain exactly 10 colors', () => {
40
+ expect(CLUSTER_COLORS).toHaveLength(10);
41
+ });
42
+ it('should contain only valid CSS hex color strings', () => {
43
+ const hexColorRegex = /^#[0-9a-fA-F]{6}$/;
44
+ for (const color of CLUSTER_COLORS) {
45
+ expect(color).toMatch(hexColorRegex);
46
+ }
47
+ });
48
+ it('should contain all unique colors', () => {
49
+ const unique = new Set(CLUSTER_COLORS);
50
+ expect(unique.size).toBe(CLUSTER_COLORS.length);
51
+ });
52
+ it('should be a plain array (not frozen or sealed)', () => {
53
+ expect(Array.isArray(CLUSTER_COLORS)).toBe(true);
54
+ });
55
+ });
56
+ // ================================================================
57
+ // Type interfaces - structural conformance
58
+ // ================================================================
59
+ describe('Type interfaces', () => {
60
+ describe('ClusterConfig', () => {
61
+ it('should accept a fully specified object', () => {
62
+ const config = {
63
+ EntityName: 'Users',
64
+ EntityDocumentID: 'abc-123',
65
+ Algorithm: 'dbscan',
66
+ K: 5,
67
+ Epsilon: 0.5,
68
+ MinPoints: 4,
69
+ DistanceMetric: 'euclidean',
70
+ MaxRecords: 1000,
71
+ Filter: "Status='Active'",
72
+ };
73
+ expect(config.Algorithm).toBe('dbscan');
74
+ expect(config.DistanceMetric).toBe('euclidean');
75
+ });
76
+ });
77
+ describe('ClusterPoint', () => {
78
+ it('should accept a conforming object', () => {
79
+ const point = {
80
+ X: 100,
81
+ Y: 200,
82
+ ClusterId: 2,
83
+ Label: 'Record A',
84
+ VectorKey: 'key-1',
85
+ Metadata: { Score: 0.95 },
86
+ };
87
+ expect(point.X).toBe(100);
88
+ expect(point.Y).toBe(200);
89
+ expect(point.Metadata).toEqual({ Score: 0.95 });
90
+ });
91
+ it('should accept outlier points with ClusterId -1', () => {
92
+ const outlier = {
93
+ X: 50,
94
+ Y: 50,
95
+ ClusterId: -1,
96
+ Label: 'Outlier',
97
+ VectorKey: 'out-1',
98
+ Metadata: {},
99
+ };
100
+ expect(outlier.ClusterId).toBe(-1);
101
+ });
102
+ });
103
+ describe('ClusterInfo', () => {
104
+ it('should accept a conforming object', () => {
105
+ const info = {
106
+ Id: 0,
107
+ Label: 'Cluster 1',
108
+ Color: '#5b8def',
109
+ MemberCount: 42,
110
+ };
111
+ expect(info.Id).toBe(0);
112
+ expect(info.MemberCount).toBe(42);
113
+ });
114
+ });
115
+ describe('ClusterMetrics', () => {
116
+ it('should accept a conforming object', () => {
117
+ const metrics = {
118
+ SilhouetteScore: 0.72,
119
+ ClusterCount: 3,
120
+ ComputationTimeMs: 150,
121
+ RecordCount: 100,
122
+ OutlierCount: 5,
123
+ };
124
+ expect(metrics.SilhouetteScore).toBe(0.72);
125
+ expect(metrics.OutlierCount).toBe(5);
126
+ });
127
+ });
128
+ describe('ClusterVisualizationResult', () => {
129
+ it('should accept a conforming object', () => {
130
+ const result = {
131
+ Points: [],
132
+ Clusters: [],
133
+ Metrics: {
134
+ SilhouetteScore: 0,
135
+ ClusterCount: 0,
136
+ ComputationTimeMs: 10,
137
+ RecordCount: 0,
138
+ OutlierCount: 0,
139
+ },
140
+ Config: DefaultClusterConfig(),
141
+ };
142
+ expect(result.Points).toHaveLength(0);
143
+ expect(result.Config.Algorithm).toBe('kmeans');
144
+ });
145
+ });
146
+ describe('ClusterInputVector', () => {
147
+ it('should accept a minimal object', () => {
148
+ const input = {
149
+ Key: 'vec-1',
150
+ Vector: [0.1, 0.2, 0.3],
151
+ };
152
+ expect(input.Key).toBe('vec-1');
153
+ expect(input.Label).toBeUndefined();
154
+ expect(input.Metadata).toBeUndefined();
155
+ });
156
+ it('should accept a fully specified object', () => {
157
+ const input = {
158
+ Key: 'vec-2',
159
+ Vector: [1.0, 2.0],
160
+ Label: 'My Vector',
161
+ Metadata: { source: 'test' },
162
+ };
163
+ expect(input.Label).toBe('My Vector');
164
+ });
165
+ });
166
+ describe('CancelableEvent', () => {
167
+ it('should accept a typed event', () => {
168
+ const event = {
169
+ Data: 'hello',
170
+ Cancel: false,
171
+ };
172
+ expect(event.Cancel).toBe(false);
173
+ event.Cancel = true;
174
+ expect(event.Cancel).toBe(true);
175
+ });
176
+ it('should default to unknown type parameter', () => {
177
+ const event = {
178
+ Data: 42,
179
+ Cancel: false,
180
+ };
181
+ expect(event.Data).toBe(42);
182
+ });
183
+ });
184
+ describe('ViewportRect', () => {
185
+ it('should accept a conforming object', () => {
186
+ const rect = {
187
+ MinX: -50,
188
+ MinY: -30,
189
+ Width: 1100,
190
+ Height: 800,
191
+ };
192
+ expect(rect.Width).toBe(1100);
193
+ });
194
+ });
195
+ describe('ClusterSelectedEvent', () => {
196
+ it('should accept a conforming object', () => {
197
+ const event = {
198
+ ClusterId: 1,
199
+ Label: 'Cluster 2',
200
+ Color: '#34d399',
201
+ MemberCount: 15,
202
+ };
203
+ expect(event.ClusterId).toBe(1);
204
+ });
205
+ });
206
+ describe('ViewportTransform', () => {
207
+ it('should accept a conforming object', () => {
208
+ const vt = {
209
+ TranslateX: 10,
210
+ TranslateY: 20,
211
+ Scale: 1.5,
212
+ };
213
+ expect(vt.Scale).toBe(1.5);
214
+ });
215
+ });
216
+ describe('ClusterLabel', () => {
217
+ it('should accept a conforming object', () => {
218
+ const label = {
219
+ ClusterId: 0,
220
+ Label: 'Revenue Leaders',
221
+ IsUserEdited: true,
222
+ };
223
+ expect(label.IsUserEdited).toBe(true);
224
+ });
225
+ });
226
+ describe('SavedClusterVisualization', () => {
227
+ it('should accept a minimal object', () => {
228
+ const saved = {
229
+ Id: 'uuid-1',
230
+ Name: 'My Analysis',
231
+ EntityName: 'Accounts',
232
+ Algorithm: 'kmeans',
233
+ Params: { K: 5 },
234
+ CreatedAt: '2024-01-01T00:00:00Z',
235
+ };
236
+ expect(saved.Result).toBeUndefined();
237
+ expect(saved.Viewport).toBeUndefined();
238
+ expect(saved.ClusterLabels).toBeUndefined();
239
+ });
240
+ });
241
+ describe('ClusterConfigPanelEntityOption', () => {
242
+ it('should accept a conforming object', () => {
243
+ const opt = { Name: 'Accounts' };
244
+ expect(opt.Name).toBe('Accounts');
245
+ });
246
+ });
247
+ describe('ClusterConfigPanelEntityDocOption', () => {
248
+ it('should accept a conforming object', () => {
249
+ const opt = { ID: 'doc-1', Name: 'Default Doc' };
250
+ expect(opt.ID).toBe('doc-1');
251
+ });
252
+ });
253
+ });
254
+ //# sourceMappingURL=clustering-types.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clustering-types.test.js","sourceRoot":"","sources":["../../src/__tests__/clustering-types.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n DefaultClusterConfig,\n CLUSTER_COLORS,\n ClusterConfig,\n ClusterPoint,\n ClusterInfo,\n ClusterMetrics,\n ClusterVisualizationResult,\n ClusterInputVector,\n CancelableEvent,\n ViewportRect,\n ClusterSelectedEvent,\n ViewportTransform,\n ClusterLabel,\n SavedClusterVisualization,\n ClusterConfigPanelEntityOption,\n ClusterConfigPanelEntityDocOption,\n} from '../lib/clustering.types';\n\n// ================================================================\n// DefaultClusterConfig factory\n// ================================================================\n\ndescribe('DefaultClusterConfig', () => {\n it('should return a new object on each call', () => {\n const a = DefaultClusterConfig();\n const b = DefaultClusterConfig();\n expect(a).not.toBe(b);\n expect(a).toEqual(b);\n });\n\n it('should have correct default values', () => {\n const cfg = DefaultClusterConfig();\n expect(cfg.EntityName).toBe('');\n expect(cfg.EntityDocumentID).toBe('');\n expect(cfg.Algorithm).toBe('kmeans');\n expect(cfg.K).toBe(4);\n expect(cfg.Epsilon).toBe(0.3);\n expect(cfg.MinPoints).toBe(3);\n expect(cfg.DistanceMetric).toBe('cosine');\n expect(cfg.MaxRecords).toBe(500);\n expect(cfg.Filter).toBe('');\n });\n\n it('should be mutable after creation', () => {\n const cfg = DefaultClusterConfig();\n cfg.EntityName = 'Companies';\n cfg.K = 8;\n cfg.Algorithm = 'dbscan';\n expect(cfg.EntityName).toBe('Companies');\n expect(cfg.K).toBe(8);\n expect(cfg.Algorithm).toBe('dbscan');\n });\n});\n\n// ================================================================\n// CLUSTER_COLORS constant\n// ================================================================\n\ndescribe('CLUSTER_COLORS', () => {\n it('should contain exactly 10 colors', () => {\n expect(CLUSTER_COLORS).toHaveLength(10);\n });\n\n it('should contain only valid CSS hex color strings', () => {\n const hexColorRegex = /^#[0-9a-fA-F]{6}$/;\n for (const color of CLUSTER_COLORS) {\n expect(color).toMatch(hexColorRegex);\n }\n });\n\n it('should contain all unique colors', () => {\n const unique = new Set(CLUSTER_COLORS);\n expect(unique.size).toBe(CLUSTER_COLORS.length);\n });\n\n it('should be a plain array (not frozen or sealed)', () => {\n expect(Array.isArray(CLUSTER_COLORS)).toBe(true);\n });\n});\n\n// ================================================================\n// Type interfaces - structural conformance\n// ================================================================\n\ndescribe('Type interfaces', () => {\n describe('ClusterConfig', () => {\n it('should accept a fully specified object', () => {\n const config: ClusterConfig = {\n EntityName: 'Users',\n EntityDocumentID: 'abc-123',\n Algorithm: 'dbscan',\n K: 5,\n Epsilon: 0.5,\n MinPoints: 4,\n DistanceMetric: 'euclidean',\n MaxRecords: 1000,\n Filter: \"Status='Active'\",\n };\n expect(config.Algorithm).toBe('dbscan');\n expect(config.DistanceMetric).toBe('euclidean');\n });\n });\n\n describe('ClusterPoint', () => {\n it('should accept a conforming object', () => {\n const point: ClusterPoint = {\n X: 100,\n Y: 200,\n ClusterId: 2,\n Label: 'Record A',\n VectorKey: 'key-1',\n Metadata: { Score: 0.95 },\n };\n expect(point.X).toBe(100);\n expect(point.Y).toBe(200);\n expect(point.Metadata).toEqual({ Score: 0.95 });\n });\n\n it('should accept outlier points with ClusterId -1', () => {\n const outlier: ClusterPoint = {\n X: 50,\n Y: 50,\n ClusterId: -1,\n Label: 'Outlier',\n VectorKey: 'out-1',\n Metadata: {},\n };\n expect(outlier.ClusterId).toBe(-1);\n });\n });\n\n describe('ClusterInfo', () => {\n it('should accept a conforming object', () => {\n const info: ClusterInfo = {\n Id: 0,\n Label: 'Cluster 1',\n Color: '#5b8def',\n MemberCount: 42,\n };\n expect(info.Id).toBe(0);\n expect(info.MemberCount).toBe(42);\n });\n });\n\n describe('ClusterMetrics', () => {\n it('should accept a conforming object', () => {\n const metrics: ClusterMetrics = {\n SilhouetteScore: 0.72,\n ClusterCount: 3,\n ComputationTimeMs: 150,\n RecordCount: 100,\n OutlierCount: 5,\n };\n expect(metrics.SilhouetteScore).toBe(0.72);\n expect(metrics.OutlierCount).toBe(5);\n });\n });\n\n describe('ClusterVisualizationResult', () => {\n it('should accept a conforming object', () => {\n const result: ClusterVisualizationResult = {\n Points: [],\n Clusters: [],\n Metrics: {\n SilhouetteScore: 0,\n ClusterCount: 0,\n ComputationTimeMs: 10,\n RecordCount: 0,\n OutlierCount: 0,\n },\n Config: DefaultClusterConfig(),\n };\n expect(result.Points).toHaveLength(0);\n expect(result.Config.Algorithm).toBe('kmeans');\n });\n });\n\n describe('ClusterInputVector', () => {\n it('should accept a minimal object', () => {\n const input: ClusterInputVector = {\n Key: 'vec-1',\n Vector: [0.1, 0.2, 0.3],\n };\n expect(input.Key).toBe('vec-1');\n expect(input.Label).toBeUndefined();\n expect(input.Metadata).toBeUndefined();\n });\n\n it('should accept a fully specified object', () => {\n const input: ClusterInputVector = {\n Key: 'vec-2',\n Vector: [1.0, 2.0],\n Label: 'My Vector',\n Metadata: { source: 'test' },\n };\n expect(input.Label).toBe('My Vector');\n });\n });\n\n describe('CancelableEvent', () => {\n it('should accept a typed event', () => {\n const event: CancelableEvent<string> = {\n Data: 'hello',\n Cancel: false,\n };\n expect(event.Cancel).toBe(false);\n event.Cancel = true;\n expect(event.Cancel).toBe(true);\n });\n\n it('should default to unknown type parameter', () => {\n const event: CancelableEvent = {\n Data: 42,\n Cancel: false,\n };\n expect(event.Data).toBe(42);\n });\n });\n\n describe('ViewportRect', () => {\n it('should accept a conforming object', () => {\n const rect: ViewportRect = {\n MinX: -50,\n MinY: -30,\n Width: 1100,\n Height: 800,\n };\n expect(rect.Width).toBe(1100);\n });\n });\n\n describe('ClusterSelectedEvent', () => {\n it('should accept a conforming object', () => {\n const event: ClusterSelectedEvent = {\n ClusterId: 1,\n Label: 'Cluster 2',\n Color: '#34d399',\n MemberCount: 15,\n };\n expect(event.ClusterId).toBe(1);\n });\n });\n\n describe('ViewportTransform', () => {\n it('should accept a conforming object', () => {\n const vt: ViewportTransform = {\n TranslateX: 10,\n TranslateY: 20,\n Scale: 1.5,\n };\n expect(vt.Scale).toBe(1.5);\n });\n });\n\n describe('ClusterLabel', () => {\n it('should accept a conforming object', () => {\n const label: ClusterLabel = {\n ClusterId: 0,\n Label: 'Revenue Leaders',\n IsUserEdited: true,\n };\n expect(label.IsUserEdited).toBe(true);\n });\n });\n\n describe('SavedClusterVisualization', () => {\n it('should accept a minimal object', () => {\n const saved: SavedClusterVisualization = {\n Id: 'uuid-1',\n Name: 'My Analysis',\n EntityName: 'Accounts',\n Algorithm: 'kmeans',\n Params: { K: 5 },\n CreatedAt: '2024-01-01T00:00:00Z',\n };\n expect(saved.Result).toBeUndefined();\n expect(saved.Viewport).toBeUndefined();\n expect(saved.ClusterLabels).toBeUndefined();\n });\n });\n\n describe('ClusterConfigPanelEntityOption', () => {\n it('should accept a conforming object', () => {\n const opt: ClusterConfigPanelEntityOption = { Name: 'Accounts' };\n expect(opt.Name).toBe('Accounts');\n });\n });\n\n describe('ClusterConfigPanelEntityDocOption', () => {\n it('should accept a conforming object', () => {\n const opt: ClusterConfigPanelEntityDocOption = { ID: 'doc-1', Name: 'Default Doc' };\n expect(opt.ID).toBe('doc-1');\n });\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('@memberjunction/ng-clustering', () => {
3
+ it('should have a passing test', () => {
4
+ expect(true).toBe(true);
5
+ });
6
+ });
7
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect } from 'vitest';\n\ndescribe('@memberjunction/ng-clustering', () => {\n it('should have a passing test', () => {\n expect(true).toBe(true);\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scatter-component-logic.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter-component-logic.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scatter-component-logic.test.ts"],"names":[],"mappings":""}