@memberjunction/ng-clustering 6.2.0-edge.0 → 6.2.0-edge.2
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/dist/__tests__/cluster-view-type.test.js +7 -7
- package/dist/__tests__/cluster-view-type.test.js.map +1 -1
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +15 -1
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -1
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js +38 -10
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -1
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts +29 -5
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-type/cluster-view-renderer.component.js +71 -23
- package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -1
- package/dist/lib/view-type/cluster-view.types.d.ts +2 -0
- package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -1
- package/dist/lib/view-type/cluster-view.types.js +5 -1
- package/dist/lib/view-type/cluster-view.types.js.map +1 -1
- package/package.json +12 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
import { NormalizeUUID } from '@memberjunction/global';
|
|
3
|
-
import { DEFAULT_CLUSTER_VIEW_CONFIG,
|
|
3
|
+
import { DEFAULT_CLUSTER_VIEW_CONFIG, ToClusterViewConfig, } from '../lib/view-type/cluster-view.types';
|
|
4
4
|
import { EntityDocumentAvailabilityEngine } from '../lib/view-type/entity-document-availability.engine';
|
|
5
5
|
// NOTE: We intentionally do NOT import ClusterViewType / the renderer here — those pull the
|
|
6
6
|
// Angular component graph (ng-entity-viewer) which needs the JIT compiler in a unit-test env.
|
|
@@ -20,22 +20,22 @@ function seedEntitiesWithVectors(entityIds) {
|
|
|
20
20
|
}
|
|
21
21
|
describe('toClusterViewConfig', () => {
|
|
22
22
|
it('returns the full defaults for empty / null input', () => {
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
23
|
+
expect(ToClusterViewConfig(null)).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);
|
|
24
|
+
expect(ToClusterViewConfig(undefined)).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);
|
|
25
|
+
expect(ToClusterViewConfig({})).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);
|
|
26
26
|
});
|
|
27
27
|
it('preserves valid provided values', () => {
|
|
28
|
-
const cfg =
|
|
28
|
+
const cfg = ToClusterViewConfig({ algorithm: 'dbscan', k: 8, dimensions: 3, colorBy: 'entity', maxRecords: 1000, nameClusters: false });
|
|
29
29
|
expect(cfg).toEqual({ algorithm: 'dbscan', k: 8, dimensions: 3, colorBy: 'entity', maxRecords: 1000, nameClusters: false });
|
|
30
30
|
});
|
|
31
31
|
it('coerces invalid enum-ish values to safe defaults', () => {
|
|
32
|
-
const cfg =
|
|
32
|
+
const cfg = ToClusterViewConfig({ algorithm: 'bogus', dimensions: 5, colorBy: 'rainbow' });
|
|
33
33
|
expect(cfg.algorithm).toBe('kmeans');
|
|
34
34
|
expect(cfg.dimensions).toBe(2);
|
|
35
35
|
expect(cfg.colorBy).toBe('cluster');
|
|
36
36
|
});
|
|
37
37
|
it('falls back to defaults for wrong-typed numerics/booleans', () => {
|
|
38
|
-
const cfg =
|
|
38
|
+
const cfg = ToClusterViewConfig({ k: 'lots', maxRecords: null, nameClusters: 'yes' });
|
|
39
39
|
expect(cfg.k).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.k);
|
|
40
40
|
expect(cfg.maxRecords).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.maxRecords);
|
|
41
41
|
expect(cfg.nameClusters).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view-type.test.js","sourceRoot":"","sources":["../../src/__tests__/cluster-view-type.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach } from 'vitest';\nimport type { EntityInfo } from '@memberjunction/core';\nimport { NormalizeUUID } from '@memberjunction/global';\nimport {\n DEFAULT_CLUSTER_VIEW_CONFIG,\n
|
|
1
|
+
{"version":3,"file":"cluster-view-type.test.js","sourceRoot":"","sources":["../../src/__tests__/cluster-view-type.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach } from 'vitest';\nimport type { EntityInfo } from '@memberjunction/core';\nimport { NormalizeUUID } from '@memberjunction/global';\nimport {\n DEFAULT_CLUSTER_VIEW_CONFIG,\n ToClusterViewConfig,\n} from '../lib/view-type/cluster-view.types';\nimport { EntityDocumentAvailabilityEngine } from '../lib/view-type/entity-document-availability.engine';\n\n// NOTE: We intentionally do NOT import ClusterViewType / the renderer here — those pull the\n// Angular component graph (ng-entity-viewer) which needs the JIT compiler in a unit-test env.\n// The Cluster descriptor's IsAvailableFor simply delegates to the availability engine tested\n// below, and the renderer/prop-sheet are exercised via the live Playwright pass.\n\n// Minimal EntityInfo stand-in — only the fields the code under test reads.\nfunction entity(id: string): EntityInfo {\n return { ID: id, Name: `Entity ${id}` } as unknown as EntityInfo;\n}\n\n/**\n * Seed the engine's vector-presence cache (the set of entity IDs that have populated vectors)\n * without hitting the database. Mirrors what `loadVectorPresence` produces.\n */\nfunction seedEntitiesWithVectors(entityIds: string[]): void {\n (EntityDocumentAvailabilityEngine.Instance as unknown as { _entityIDsWithVectors: Set<string> })._entityIDsWithVectors =\n new Set(entityIds.map(id => NormalizeUUID(id)));\n}\n\ndescribe('toClusterViewConfig', () => {\n it('returns the full defaults for empty / null input', () => {\n expect(ToClusterViewConfig(null)).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);\n expect(ToClusterViewConfig(undefined)).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);\n expect(ToClusterViewConfig({})).toEqual(DEFAULT_CLUSTER_VIEW_CONFIG);\n });\n\n it('preserves valid provided values', () => {\n const cfg = ToClusterViewConfig({ algorithm: 'dbscan', k: 8, dimensions: 3, colorBy: 'entity', maxRecords: 1000, nameClusters: false });\n expect(cfg).toEqual({ algorithm: 'dbscan', k: 8, dimensions: 3, colorBy: 'entity', maxRecords: 1000, nameClusters: false });\n });\n\n it('coerces invalid enum-ish values to safe defaults', () => {\n const cfg = ToClusterViewConfig({ algorithm: 'bogus', dimensions: 5, colorBy: 'rainbow' });\n expect(cfg.algorithm).toBe('kmeans');\n expect(cfg.dimensions).toBe(2);\n expect(cfg.colorBy).toBe('cluster');\n });\n\n it('falls back to defaults for wrong-typed numerics/booleans', () => {\n const cfg = ToClusterViewConfig({ k: 'lots', maxRecords: null, nameClusters: 'yes' });\n expect(cfg.k).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.k);\n expect(cfg.maxRecords).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.maxRecords);\n expect(cfg.nameClusters).toBe(DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters);\n });\n});\n\ndescribe('EntityDocumentAvailabilityEngine.HasVectorsForEntity', () => {\n beforeEach(() => seedEntitiesWithVectors([]));\n\n it('returns false when no entity is supplied', () => {\n expect(EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(null)).toBe(false);\n });\n\n it('returns false when the entity has no populated vectors', () => {\n seedEntitiesWithVectors(['AAAAAAAA-0000-0000-0000-000000000001']);\n expect(EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(entity('BBBBBBBB-0000-0000-0000-000000000002'))).toBe(false);\n });\n\n it('returns true when the entity has populated vectors', () => {\n const id = 'AAAAAAAA-0000-0000-0000-000000000001';\n seedEntitiesWithVectors([id]);\n expect(EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(entity(id))).toBe(true);\n });\n\n it('matches UUIDs case-insensitively (SQL Server vs PostgreSQL casing)', () => {\n seedEntitiesWithVectors(['aaaaaaaa-0000-0000-0000-000000000001']);\n expect(EntityDocumentAvailabilityEngine.Instance.HasVectorsForEntity(entity('AAAAAAAA-0000-0000-0000-000000000001'))).toBe(true);\n });\n});\n"]}
|
|
@@ -18,12 +18,26 @@ export declare class ClusterViewPropSheetComponent implements IViewPropSheet<Rec
|
|
|
18
18
|
get config(): Record<string, unknown>;
|
|
19
19
|
configChange: EventEmitter<Record<string, unknown>>;
|
|
20
20
|
/** The current parsed config (defaults applied) bound by the template. */
|
|
21
|
-
|
|
21
|
+
Cfg: ClusterViewConfig;
|
|
22
|
+
/** @deprecated Use {@link Cfg}. */
|
|
23
|
+
get cfg(): ClusterViewConfig;
|
|
24
|
+
/** @deprecated Use {@link Cfg}. */
|
|
25
|
+
set cfg(value: ClusterViewConfig);
|
|
22
26
|
/** Apply a partial change, re-emit the full config map to the host. */
|
|
27
|
+
Patch(change: Partial<ClusterViewConfig>): void;
|
|
28
|
+
/** @deprecated Use {@link Patch}. */
|
|
23
29
|
patch(change: Partial<ClusterViewConfig>): void;
|
|
30
|
+
AsAlgorithm(v: string): 'kmeans' | 'dbscan';
|
|
31
|
+
/** @deprecated Use {@link AsAlgorithm}. */
|
|
24
32
|
asAlgorithm(v: string): 'kmeans' | 'dbscan';
|
|
33
|
+
AsDimensions(v: string): 2 | 3;
|
|
34
|
+
/** @deprecated Use {@link AsDimensions}. */
|
|
25
35
|
asDimensions(v: string): 2 | 3;
|
|
36
|
+
AsColorBy(v: string): 'cluster' | 'entity';
|
|
37
|
+
/** @deprecated Use {@link AsColorBy}. */
|
|
26
38
|
asColorBy(v: string): 'cluster' | 'entity';
|
|
39
|
+
AsInt(v: string, fallback: number): number;
|
|
40
|
+
/** @deprecated Use {@link AsInt}. */
|
|
27
41
|
asInt(v: string, fallback: number): number;
|
|
28
42
|
static ɵfac: i0.ɵɵFactoryDeclaration<ClusterViewPropSheetComponent, never>;
|
|
29
43
|
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>;
|
|
@@ -1 +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;
|
|
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,mCAAmC;IACnC,IAAW,GAAG,IAAI,iBAAiB,CAElC;IACD,mCAAmC;IACnC,IAAW,GAAG,CAAC,KAAK,EAAE,iBAAiB,EAEtC;IAED,uEAAuE;IACvE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAM/C,qCAAqC;IACrC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAI/C,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ;IAI3C,2CAA2C;IAC3C,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ;IAG3C,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC;IAI9B,4CAA4C;IAC5C,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC;IAG9B,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ;IAI1C,yCAAyC;IACzC,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ;IAG1C,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAK1C,qCAAqC;IACrC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;yCArE/B,6BAA6B;2CAA7B,6BAA6B;CAwEzC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
|
|
2
2
|
import { EntityInfo } from '@memberjunction/core';
|
|
3
|
-
import {
|
|
3
|
+
import { ToClusterViewConfig } from './cluster-view.types';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
import * as i1 from "@angular/forms";
|
|
6
6
|
function ClusterViewPropSheetComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
@@ -30,34 +30,62 @@ export class ClusterViewPropSheetComponent {
|
|
|
30
30
|
this._config = {};
|
|
31
31
|
this.configChange = new EventEmitter();
|
|
32
32
|
/** The current parsed config (defaults applied) bound by the template. */
|
|
33
|
-
this.
|
|
33
|
+
this.Cfg = ToClusterViewConfig({});
|
|
34
34
|
}
|
|
35
35
|
set config(value) {
|
|
36
36
|
this._config = value ?? {};
|
|
37
|
-
this.
|
|
37
|
+
this.Cfg = ToClusterViewConfig(this._config);
|
|
38
38
|
}
|
|
39
39
|
get config() {
|
|
40
40
|
return this._config;
|
|
41
41
|
}
|
|
42
|
+
/** @deprecated Use {@link Cfg}. */
|
|
43
|
+
get cfg() {
|
|
44
|
+
return this.Cfg;
|
|
45
|
+
}
|
|
46
|
+
/** @deprecated Use {@link Cfg}. */
|
|
47
|
+
set cfg(value) {
|
|
48
|
+
this.Cfg = value;
|
|
49
|
+
}
|
|
42
50
|
/** Apply a partial change, re-emit the full config map to the host. */
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
this._config = { ...this.
|
|
51
|
+
Patch(change) {
|
|
52
|
+
this.Cfg = { ...this.Cfg, ...change };
|
|
53
|
+
this._config = { ...this.Cfg };
|
|
46
54
|
this.configChange.emit(this._config);
|
|
47
55
|
}
|
|
48
|
-
|
|
56
|
+
/** @deprecated Use {@link Patch}. */
|
|
57
|
+
patch(change) {
|
|
58
|
+
return this.Patch(change);
|
|
59
|
+
}
|
|
60
|
+
AsAlgorithm(v) {
|
|
49
61
|
return v === 'dbscan' ? 'dbscan' : 'kmeans';
|
|
50
62
|
}
|
|
51
|
-
|
|
63
|
+
/** @deprecated Use {@link AsAlgorithm}. */
|
|
64
|
+
asAlgorithm(v) {
|
|
65
|
+
return this.AsAlgorithm(v);
|
|
66
|
+
}
|
|
67
|
+
AsDimensions(v) {
|
|
52
68
|
return String(v) === '3' ? 3 : 2;
|
|
53
69
|
}
|
|
54
|
-
|
|
70
|
+
/** @deprecated Use {@link AsDimensions}. */
|
|
71
|
+
asDimensions(v) {
|
|
72
|
+
return this.AsDimensions(v);
|
|
73
|
+
}
|
|
74
|
+
AsColorBy(v) {
|
|
55
75
|
return v === 'entity' ? 'entity' : 'cluster';
|
|
56
76
|
}
|
|
57
|
-
|
|
77
|
+
/** @deprecated Use {@link AsColorBy}. */
|
|
78
|
+
asColorBy(v) {
|
|
79
|
+
return this.AsColorBy(v);
|
|
80
|
+
}
|
|
81
|
+
AsInt(v, fallback) {
|
|
58
82
|
const n = parseInt(v, 10);
|
|
59
83
|
return Number.isNaN(n) ? fallback : n;
|
|
60
84
|
}
|
|
85
|
+
/** @deprecated Use {@link AsInt}. */
|
|
86
|
+
asInt(v, fallback) {
|
|
87
|
+
return this.AsInt(v, fallback);
|
|
88
|
+
}
|
|
61
89
|
static { this.ɵfac = function ClusterViewPropSheetComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ClusterViewPropSheetComponent)(); }; }
|
|
62
90
|
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ClusterViewPropSheetComponent, selectors: [["mj-cluster-view-prop-sheet"]], inputs: { entity: "entity", config: "config" }, outputs: { configChange: "configChange" }, standalone: false, decls: 34, vars: 8, consts: [[1, "cluster-prop-sheet"], [1, "cluster-prop-row"], [1, "mj-input", 3, "change", "value"], ["value", "kmeans"], ["value", "dbscan"], [3, "value"], ["value", "cluster"], ["value", "entity"], ["type", "number", "min", "50", "max", "5000", "step", "50", 1, "mj-input", 3, "input", "value"], [1, "cluster-prop-row", "cluster-prop-row--inline"], ["type", "checkbox", 1, "mj-checkbox", 3, "change", "checked"], ["type", "number", "min", "2", "max", "20", 1, "mj-input", 3, "input", "value"]], template: function ClusterViewPropSheetComponent_Template(rf, ctx) { if (rf & 1) {
|
|
63
91
|
i0.ɵɵelementStart(0, "div", 0)(1, "label", 1)(2, "span");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view-prop-sheet.component.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-prop-sheet.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAqB,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;;;;;IA0BpE,AADF,gCAAgC,WACxB;IAAA,4BAAY;IAAA,iBAAO;IACzB,iCACyE;IAAlE,sMAAS,aAAM,KAAK,+CAAuC,EAAE,CAAC,KAAC;IACxE,AAFE,iBACyE,EACnE;;;IAFiD,eAAe;IAAf,oCAAe;;AAzBhF;;;;;;;GAOG;AA0DH,MAAM,OAAO,6BAA6B;IAzD1C;QA0DW,WAAM,GAAsB,IAAI,CAAC;QAElC,YAAO,GAA4B,EAAE,CAAC;QAUpC,iBAAY,GAAG,IAAI,YAAY,EAA2B,CAAC;QAErE,0EAA0E;QACnE,QAAG,GAAsB,mBAAmB,CAAC,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cluster-view-prop-sheet.component.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-prop-sheet.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';\nimport { EntityInfo } from '@memberjunction/core';\nimport { IViewPropSheet } from '@memberjunction/ng-entity-viewer';\nimport { ClusterViewConfig, ToClusterViewConfig } from './cluster-view.types';\n\n/**\n * ClusterViewPropSheetComponent\n * -----------------------------\n * The configuration prop-sheet for the Cluster view type — honors {@link IViewPropSheet}.\n * Lets the user pick the algorithm, cluster count, 2D/3D layout, legend coloring, record cap,\n * and LLM naming. Emits the updated config (as the opaque host map) on every change so the host\n * can persist it into `UserView.DisplayState.viewTypeConfigs` and re-render the cluster.\n */\n@Component({\n standalone: false,\n selector: 'mj-cluster-view-prop-sheet',\n encapsulation: ViewEncapsulation.None,\n template: `\n <div class=\"cluster-prop-sheet\">\n <label class=\"cluster-prop-row\">\n <span>Algorithm</span>\n <select class=\"mj-input\" [value]=\"cfg.algorithm\" (change)=\"patch({ algorithm: asAlgorithm($any($event.target).value) })\">\n <option value=\"kmeans\">K-Means</option>\n <option value=\"dbscan\">DBSCAN</option>\n </select>\n </label>\n\n @if (cfg.algorithm === 'kmeans') {\n <label class=\"cluster-prop-row\">\n <span>Clusters (K)</span>\n <input class=\"mj-input\" type=\"number\" min=\"2\" max=\"20\" [value]=\"cfg.k\"\n (input)=\"patch({ k: asInt($any($event.target).value, cfg.k) })\" />\n </label>\n }\n\n <label class=\"cluster-prop-row\">\n <span>Layout</span>\n <select class=\"mj-input\" [value]=\"cfg.dimensions\" (change)=\"patch({ dimensions: asDimensions($any($event.target).value) })\">\n <option [value]=\"2\">2D</option>\n <option [value]=\"3\">3D</option>\n </select>\n </label>\n\n <label class=\"cluster-prop-row\">\n <span>Color by</span>\n <select class=\"mj-input\" [value]=\"cfg.colorBy\" (change)=\"patch({ colorBy: asColorBy($any($event.target).value) })\">\n <option value=\"cluster\">Cluster</option>\n <option value=\"entity\">Entity</option>\n </select>\n </label>\n\n <label class=\"cluster-prop-row\">\n <span>Max records</span>\n <input class=\"mj-input\" type=\"number\" min=\"50\" max=\"5000\" step=\"50\" [value]=\"cfg.maxRecords\"\n (input)=\"patch({ maxRecords: asInt($any($event.target).value, cfg.maxRecords) })\" />\n </label>\n\n <label class=\"cluster-prop-row cluster-prop-row--inline\">\n <input class=\"mj-checkbox\" type=\"checkbox\" [checked]=\"cfg.nameClusters\"\n (change)=\"patch({ nameClusters: $any($event.target).checked })\" />\n <span>Name clusters with AI</span>\n </label>\n </div>\n `,\n styles: [`\n .cluster-prop-sheet { display: flex; flex-direction: column; gap: 12px; padding: 12px; }\n .cluster-prop-row { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--mj-text-secondary); }\n .cluster-prop-row--inline { flex-direction: row; align-items: center; gap: 8px; }\n `],\n})\nexport class ClusterViewPropSheetComponent implements IViewPropSheet<Record<string, unknown>> {\n @Input() entity: EntityInfo | null = null;\n\n private _config: Record<string, unknown> = {};\n @Input()\n set config(value: Record<string, unknown>) {\n this._config = value ?? {};\n this.Cfg = ToClusterViewConfig(this._config);\n }\n get config(): Record<string, unknown> {\n return this._config;\n }\n\n @Output() configChange = new EventEmitter<Record<string, unknown>>();\n\n /** The current parsed config (defaults applied) bound by the template. */\n public Cfg: ClusterViewConfig = ToClusterViewConfig({});\n\n /** @deprecated Use {@link Cfg}. */\n public get cfg(): ClusterViewConfig {\n return this.Cfg;\n }\n /** @deprecated Use {@link Cfg}. */\n public set cfg(value: ClusterViewConfig) {\n this.Cfg = value;\n }\n\n /** Apply a partial change, re-emit the full config map to the host. */\n Patch(change: Partial<ClusterViewConfig>): void {\n this.Cfg = { ...this.Cfg, ...change };\n this._config = { ...this.Cfg } as unknown as Record<string, unknown>;\n this.configChange.emit(this._config);\n }\n\n /** @deprecated Use {@link Patch}. */\n patch(change: Partial<ClusterViewConfig>): void {\n return this.Patch(change);\n }\n\n AsAlgorithm(v: string): 'kmeans' | 'dbscan' {\n return v === 'dbscan' ? 'dbscan' : 'kmeans';\n }\n\n /** @deprecated Use {@link AsAlgorithm}. */\n asAlgorithm(v: string): 'kmeans' | 'dbscan' {\n return this.AsAlgorithm(v);\n }\n AsDimensions(v: string): 2 | 3 {\n return String(v) === '3' ? 3 : 2;\n }\n\n /** @deprecated Use {@link AsDimensions}. */\n asDimensions(v: string): 2 | 3 {\n return this.AsDimensions(v);\n }\n AsColorBy(v: string): 'cluster' | 'entity' {\n return v === 'entity' ? 'entity' : 'cluster';\n }\n\n /** @deprecated Use {@link AsColorBy}. */\n asColorBy(v: string): 'cluster' | 'entity' {\n return this.AsColorBy(v);\n }\n AsInt(v: string, fallback: number): number {\n const n = parseInt(v, 10);\n return Number.isNaN(n) ? fallback : n;\n }\n\n /** @deprecated Use {@link AsInt}. */\n asInt(v: string, fallback: number): number {\n return this.AsInt(v, fallback);\n }\n}\n"]}
|
|
@@ -47,13 +47,33 @@ export declare class ClusterViewRendererComponent extends BaseAngularComponent i
|
|
|
47
47
|
recordOpened: EventEmitter<unknown>;
|
|
48
48
|
configChanged: EventEmitter<Record<string, unknown>>;
|
|
49
49
|
/** The current parsed config (defaults applied). */
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
ActiveConfig: ClusterViewConfig;
|
|
51
|
+
/** @deprecated Use {@link ActiveConfig}. */
|
|
52
|
+
get activeConfig(): ClusterViewConfig;
|
|
53
|
+
/** @deprecated Use {@link ActiveConfig}. */
|
|
54
|
+
set activeConfig(value: ClusterViewConfig);
|
|
55
|
+
Points: ClusterPoint[];
|
|
56
|
+
/** @deprecated Use {@link Points}. */
|
|
57
|
+
get points(): ClusterPoint[];
|
|
58
|
+
/** @deprecated Use {@link Points}. */
|
|
59
|
+
set points(value: ClusterPoint[]);
|
|
60
|
+
Clusters: ClusterInfo[];
|
|
61
|
+
/** @deprecated Use {@link Clusters}. */
|
|
62
|
+
get clusters(): ClusterInfo[];
|
|
63
|
+
/** @deprecated Use {@link Clusters}. */
|
|
64
|
+
set clusters(value: ClusterInfo[]);
|
|
53
65
|
/** Ordered field keys for prioritized display in the scatter tooltip / detail panel. */
|
|
54
|
-
|
|
66
|
+
FieldPriority: string[];
|
|
67
|
+
/** @deprecated Use {@link FieldPriority}. */
|
|
68
|
+
get fieldPriority(): string[];
|
|
69
|
+
/** @deprecated Use {@link FieldPriority}. */
|
|
70
|
+
set fieldPriority(value: string[]);
|
|
55
71
|
/** Map of field names → human-readable display names for the scatter tooltip / detail panel. */
|
|
56
|
-
|
|
72
|
+
FieldDisplayNames: Record<string, string>;
|
|
73
|
+
/** @deprecated Use {@link FieldDisplayNames}. */
|
|
74
|
+
get fieldDisplayNames(): Record<string, string>;
|
|
75
|
+
/** @deprecated Use {@link FieldDisplayNames}. */
|
|
76
|
+
set fieldDisplayNames(value: Record<string, string>);
|
|
57
77
|
isLoading: boolean;
|
|
58
78
|
errorMessage: string | null;
|
|
59
79
|
private recluster$;
|
|
@@ -129,7 +149,11 @@ export declare class ClusterViewRendererComponent extends BaseAngularComponent i
|
|
|
129
149
|
private buildClusterDataForPrompt;
|
|
130
150
|
/** Apply LLM-generated labels onto the live {@link clusters} array by cluster ID. */
|
|
131
151
|
private applyLabelsToClusters;
|
|
152
|
+
OnPointClicked(point: ClusterPoint): void;
|
|
153
|
+
/** @deprecated Use {@link OnPointClicked}. */
|
|
132
154
|
onPointClicked(point: ClusterPoint): void;
|
|
155
|
+
OnOpenRecordRequested(point: ClusterPoint): void;
|
|
156
|
+
/** @deprecated Use {@link OnOpenRecordRequested}. */
|
|
133
157
|
onOpenRecordRequested(point: ClusterPoint): void;
|
|
134
158
|
/**
|
|
135
159
|
* Resolve the host record corresponding to a scatter point. The vector key is the Entity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view-renderer.component.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-renderer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,eAAe,CAAC;AAGxI,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AAIrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEjE,OAAO,EAEL,YAAY,EACZ,WAAW,EAKZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAuB,MAAM,sBAAsB,CAAC;;AAG9E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAiEa,4BAA6B,SAAQ,oBAAqB,YAAW,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS;
|
|
1
|
+
{"version":3,"file":"cluster-view-renderer.component.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-renderer.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAA6B,MAAM,eAAe,CAAC;AAGxI,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AAIrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEjE,OAAO,EAEL,YAAY,EACZ,WAAW,EAKZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAuB,MAAM,sBAAsB,CAAC;;AAG9E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAiEa,4BAA6B,SAAQ,oBAAqB,YAAW,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS;IAsH7H,OAAO,CAAC,GAAG;IAnHvB,OAAO,CAAC,OAAO,CAA2B;IAC1C,IACI,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,EAMlC;IACD,IAAI,MAAM,IAAI,UAAU,GAAG,IAAI,CAE9B;IAED,kFAAkF;IACzE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAM;IAExC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,OAAO,CAA+B;IAC9C,IACI,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAYxC;IACD,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEpC;IAIS,cAAc,wBAA+B;IAC7C,YAAY,wBAA+B;IAC3C,aAAa,wCAA+C;IAItE,oDAAoD;IAC7C,YAAY,EAAE,iBAAiB,CAA2B;IAEjE,4CAA4C;IAC5C,IAAW,YAAY,IAAI,iBAAiB,CAE3C;IACD,4CAA4C;IAC5C,IAAW,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAE/C;IACM,MAAM,EAAE,YAAY,EAAE,CAAM;IAEnC,sCAAsC;IACtC,IAAW,MAAM,IAAI,YAAY,EAAE,CAElC;IACD,sCAAsC;IACtC,IAAW,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,EAEtC;IACM,QAAQ,EAAE,WAAW,EAAE,CAAM;IAEpC,wCAAwC;IACxC,IAAW,QAAQ,IAAI,WAAW,EAAE,CAEnC;IACD,wCAAwC;IACxC,IAAW,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,EAEvC;IACD,wFAAwF;IACjF,aAAa,EAAE,MAAM,EAAE,CAAM;IAEpC,6CAA6C;IAC7C,IAAW,aAAa,IAAI,MAAM,EAAE,CAEnC;IACD,6CAA6C;IAC7C,IAAW,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAEvC;IACD,gGAAgG;IACzF,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAEtD,iDAAiD;IACjD,IAAW,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAErD;IACD,iDAAiD;IACjD,IAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAEzD;IACM,SAAS,UAAS;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,iBAAiB,CAA6B;IAEtD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAK;gBAED,GAAG,EAAE,iBAAiB;IAI1C,QAAQ,IAAI,IAAI;IAWhB,WAAW,IAAI,IAAI;IAKnB,OAAO,CAAC,iBAAiB;IAIzB;;;;;;;OAOG;YACW,aAAa;IA2D3B,6FAA6F;IAC7F,OAAO,CAAC,OAAO;IAIf,oFAAoF;IACpF,OAAO,CAAC,WAAW;IAUnB,oEAAoE;IACpE,OAAO,CAAC,UAAU;IAWlB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAK9B;;;;OAIG;YACW,uBAAuB;IAQrC;;;OAGG;YACW,YAAY;IA4B1B,uFAAuF;IACvF,OAAO,CAAC,mBAAmB;IAQ3B;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAclB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAkB5B,qFAAqF;IACrF,OAAO,CAAC,kBAAkB;IAmB1B;;;;;OAKG;YACW,2BAA2B;IAsCzC,0FAA0F;IAC1F,OAAO,CAAC,yBAAyB;IA4BjC,qFAAqF;IACrF,OAAO,CAAC,qBAAqB;IAa7B,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIzC,8CAA8C;IAC9C,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIzC,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIhD,qDAAqD;IACrD,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIhD;;;;OAIG;IACH,OAAO,CAAC,cAAc;yCAzdX,4BAA4B;2CAA5B,4BAA4B;CAuexC"}
|
|
@@ -8,7 +8,7 @@ import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
|
8
8
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
9
9
|
import { ClusteringService } from '../clustering.service';
|
|
10
10
|
import { DefaultClusterConfig, } from '../clustering.types';
|
|
11
|
-
import {
|
|
11
|
+
import { ToClusterViewConfig } from './cluster-view.types';
|
|
12
12
|
import { EntityDocumentAvailabilityEngine } from './entity-document-availability.engine';
|
|
13
13
|
import * as i0 from "@angular/core";
|
|
14
14
|
import * as i1 from "../cluster-scatter.component";
|
|
@@ -77,14 +77,14 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
77
77
|
return this._entity;
|
|
78
78
|
}
|
|
79
79
|
set config(value) {
|
|
80
|
-
const next =
|
|
80
|
+
const next = ToClusterViewConfig(value ?? {});
|
|
81
81
|
// Only re-cluster when the clustering-relevant config actually changed. The host re-pushes the
|
|
82
82
|
// (unchanged) config on unrelated events — notably when a point is clicked / selection changes —
|
|
83
83
|
// and re-clustering on every push made clicking a point recompute the whole scatter. Comparing
|
|
84
84
|
// the normalized config keeps clustering stable across those no-op re-pushes.
|
|
85
|
-
const changed = JSON.stringify(next) !== JSON.stringify(this.
|
|
85
|
+
const changed = JSON.stringify(next) !== JSON.stringify(this.ActiveConfig);
|
|
86
86
|
this._config = value ?? {};
|
|
87
|
-
this.
|
|
87
|
+
this.ActiveConfig = next;
|
|
88
88
|
if (changed) {
|
|
89
89
|
this.scheduleRecluster();
|
|
90
90
|
}
|
|
@@ -92,6 +92,46 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
92
92
|
get config() {
|
|
93
93
|
return this._config;
|
|
94
94
|
}
|
|
95
|
+
/** @deprecated Use {@link ActiveConfig}. */
|
|
96
|
+
get activeConfig() {
|
|
97
|
+
return this.ActiveConfig;
|
|
98
|
+
}
|
|
99
|
+
/** @deprecated Use {@link ActiveConfig}. */
|
|
100
|
+
set activeConfig(value) {
|
|
101
|
+
this.ActiveConfig = value;
|
|
102
|
+
}
|
|
103
|
+
/** @deprecated Use {@link Points}. */
|
|
104
|
+
get points() {
|
|
105
|
+
return this.Points;
|
|
106
|
+
}
|
|
107
|
+
/** @deprecated Use {@link Points}. */
|
|
108
|
+
set points(value) {
|
|
109
|
+
this.Points = value;
|
|
110
|
+
}
|
|
111
|
+
/** @deprecated Use {@link Clusters}. */
|
|
112
|
+
get clusters() {
|
|
113
|
+
return this.Clusters;
|
|
114
|
+
}
|
|
115
|
+
/** @deprecated Use {@link Clusters}. */
|
|
116
|
+
set clusters(value) {
|
|
117
|
+
this.Clusters = value;
|
|
118
|
+
}
|
|
119
|
+
/** @deprecated Use {@link FieldPriority}. */
|
|
120
|
+
get fieldPriority() {
|
|
121
|
+
return this.FieldPriority;
|
|
122
|
+
}
|
|
123
|
+
/** @deprecated Use {@link FieldPriority}. */
|
|
124
|
+
set fieldPriority(value) {
|
|
125
|
+
this.FieldPriority = value;
|
|
126
|
+
}
|
|
127
|
+
/** @deprecated Use {@link FieldDisplayNames}. */
|
|
128
|
+
get fieldDisplayNames() {
|
|
129
|
+
return this.FieldDisplayNames;
|
|
130
|
+
}
|
|
131
|
+
/** @deprecated Use {@link FieldDisplayNames}. */
|
|
132
|
+
set fieldDisplayNames(value) {
|
|
133
|
+
this.FieldDisplayNames = value;
|
|
134
|
+
}
|
|
95
135
|
constructor(cdr) {
|
|
96
136
|
super();
|
|
97
137
|
this.cdr = cdr;
|
|
@@ -108,13 +148,13 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
108
148
|
this.configChanged = new EventEmitter();
|
|
109
149
|
// ---- Render state ----
|
|
110
150
|
/** The current parsed config (defaults applied). */
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
151
|
+
this.ActiveConfig = ToClusterViewConfig({});
|
|
152
|
+
this.Points = [];
|
|
153
|
+
this.Clusters = [];
|
|
114
154
|
/** Ordered field keys for prioritized display in the scatter tooltip / detail panel. */
|
|
115
|
-
this.
|
|
155
|
+
this.FieldPriority = [];
|
|
116
156
|
/** Map of field names → human-readable display names for the scatter tooltip / detail panel. */
|
|
117
|
-
this.
|
|
157
|
+
this.FieldDisplayNames = {};
|
|
118
158
|
this.isLoading = false;
|
|
119
159
|
this.errorMessage = null;
|
|
120
160
|
this.recluster$ = new Subject();
|
|
@@ -157,8 +197,8 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
157
197
|
const token = ++this.runToken;
|
|
158
198
|
const entity = this._entity;
|
|
159
199
|
if (!entity) {
|
|
160
|
-
this.
|
|
161
|
-
this.
|
|
200
|
+
this.Points = [];
|
|
201
|
+
this.Clusters = [];
|
|
162
202
|
this.cdr.detectChanges();
|
|
163
203
|
return;
|
|
164
204
|
}
|
|
@@ -193,11 +233,11 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
193
233
|
if (this.isStale(token)) {
|
|
194
234
|
return;
|
|
195
235
|
}
|
|
196
|
-
this.
|
|
236
|
+
this.FieldPriority = this.computeFieldPriority(entity);
|
|
197
237
|
this.applyResult(token, result.Points, result.Clusters);
|
|
198
238
|
// Fire LLM cluster naming in the background (non-blocking) when enabled. Clusters render
|
|
199
239
|
// immediately with provisional names; semantic labels appear when the LLM responds.
|
|
200
|
-
if (this.
|
|
240
|
+
if (this.ActiveConfig.nameClusters) {
|
|
201
241
|
void this.requestClusterLabelsFromLLM(gqlProvider, token, result);
|
|
202
242
|
}
|
|
203
243
|
}
|
|
@@ -214,8 +254,8 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
214
254
|
if (this.isStale(token)) {
|
|
215
255
|
return;
|
|
216
256
|
}
|
|
217
|
-
this.
|
|
218
|
-
this.
|
|
257
|
+
this.Points = points;
|
|
258
|
+
this.Clusters = clusters;
|
|
219
259
|
this.isLoading = false;
|
|
220
260
|
this.cdr.detectChanges();
|
|
221
261
|
}
|
|
@@ -225,8 +265,8 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
225
265
|
return;
|
|
226
266
|
}
|
|
227
267
|
this.errorMessage = message;
|
|
228
|
-
this.
|
|
229
|
-
this.
|
|
268
|
+
this.Points = [];
|
|
269
|
+
this.Clusters = [];
|
|
230
270
|
this.isLoading = false;
|
|
231
271
|
this.cdr.detectChanges();
|
|
232
272
|
}
|
|
@@ -258,7 +298,7 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
258
298
|
const aiClient = new GraphQLAIClient(gqlProvider);
|
|
259
299
|
const result = await aiClient.FetchEntityVectors({
|
|
260
300
|
entityDocumentID: docID,
|
|
261
|
-
maxRecords: this.
|
|
301
|
+
maxRecords: this.ActiveConfig.maxRecords,
|
|
262
302
|
filter: this.filterText?.trim() ? this.filterText.trim() : undefined,
|
|
263
303
|
});
|
|
264
304
|
if (!result.Success || result.Results.length === 0) {
|
|
@@ -318,7 +358,7 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
318
358
|
for (const f of entity.Fields) {
|
|
319
359
|
displayNames[f.Name] = f.DisplayNameOrName;
|
|
320
360
|
}
|
|
321
|
-
this.
|
|
361
|
+
this.FieldDisplayNames = displayNames;
|
|
322
362
|
return entity.Fields.filter((f) => !internalKeys.has(f.Name) && !f.IsVirtual && !f.IsPrimaryKey)
|
|
323
363
|
.sort((a, b) => {
|
|
324
364
|
if (a.IsNameField !== b.IsNameField)
|
|
@@ -331,7 +371,7 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
331
371
|
}
|
|
332
372
|
/** Build the engine {@link ClusterConfig} from the entity + the view-type config. */
|
|
333
373
|
buildClusterConfig(entity, docID) {
|
|
334
|
-
const c = this.
|
|
374
|
+
const c = this.ActiveConfig;
|
|
335
375
|
return {
|
|
336
376
|
...DefaultClusterConfig(),
|
|
337
377
|
EntityName: entity.Name,
|
|
@@ -415,7 +455,7 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
415
455
|
/** Apply LLM-generated labels onto the live {@link clusters} array by cluster ID. */
|
|
416
456
|
applyLabelsToClusters(labels) {
|
|
417
457
|
for (const label of labels) {
|
|
418
|
-
const cluster = this.
|
|
458
|
+
const cluster = this.Clusters.find((c) => c.Id === label.ClusterId);
|
|
419
459
|
if (cluster) {
|
|
420
460
|
cluster.Label = label.Label;
|
|
421
461
|
}
|
|
@@ -424,12 +464,20 @@ export class ClusterViewRendererComponent extends BaseAngularComponent {
|
|
|
424
464
|
// ================================================================
|
|
425
465
|
// Point → record mapping
|
|
426
466
|
// ================================================================
|
|
427
|
-
|
|
467
|
+
OnPointClicked(point) {
|
|
428
468
|
this.recordSelected.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });
|
|
429
469
|
}
|
|
430
|
-
|
|
470
|
+
/** @deprecated Use {@link OnPointClicked}. */
|
|
471
|
+
onPointClicked(point) {
|
|
472
|
+
return this.OnPointClicked(point);
|
|
473
|
+
}
|
|
474
|
+
OnOpenRecordRequested(point) {
|
|
431
475
|
this.recordOpened.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });
|
|
432
476
|
}
|
|
477
|
+
/** @deprecated Use {@link OnOpenRecordRequested}. */
|
|
478
|
+
onOpenRecordRequested(point) {
|
|
479
|
+
return this.OnOpenRecordRequested(point);
|
|
480
|
+
}
|
|
433
481
|
/**
|
|
434
482
|
* Resolve the host record corresponding to a scatter point. The vector key is the Entity
|
|
435
483
|
* Record Document ID, so match on the point metadata's `RecordID` (the underlying entity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view-renderer.component.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-renderer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ChangeDetectorRef, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { debounceTime, takeUntil } from 'rxjs/operators';\nimport { EntityInfo, IMetadataProvider } from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { GraphQLDataProvider, GraphQLAIClient } from '@memberjunction/graphql-dataprovider';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { IViewRenderer } from '@memberjunction/ng-entity-viewer';\nimport { ClusteringService } from '../clustering.service';\nimport {\n ClusterConfig,\n ClusterPoint,\n ClusterInfo,\n ClusterInputVector,\n ClusterLabel,\n ClusterVisualizationResult,\n DefaultClusterConfig,\n} from '../clustering.types';\nimport { ClusterViewConfig, toClusterViewConfig } from './cluster-view.types';\nimport { EntityDocumentAvailabilityEngine } from './entity-document-availability.engine';\n\n/**\n * ClusterViewRendererComponent\n * ----------------------------\n * The Cluster **view type** renderer — a thin {@link IViewRenderer} adapter that lets any\n * entity with vectors be visualized as a cluster scatter directly inside `mj-entity-viewer`,\n * reusing the exact same {@link ClusteringService} + `mj-cluster-scatter` the Knowledge Hub\n * \"Visualize\" cluster surface uses.\n *\n * The host feeds it the standard renderer inputs (entity / records / filterText / config) via\n * `setInput`, and listens for `recordSelected` / `recordOpened`. When the entity or config\n * changes it re-runs the clustering pipeline and rebinds the scatter.\n *\n * **Why this mirrors the Knowledge Hub resource rather than the server `RunClusterAnalysis`\n * path:** the server analysis returns points keyed only by record ID (so labels render as\n * \"Unknown\"), produces generic \"Cluster N\" names, and picks an Entity Document\n * non-deterministically when none is supplied (causing flip-flop \"no vectors\" results). This\n * renderer instead:\n * 1. Resolves the entity's **first active Entity Document explicitly** (deterministic source).\n * 2. Fetches that document's vectors (with their JSON metadata) via {@link GraphQLAIClient.FetchEntityVectors}.\n * 3. Builds real point labels from the entity's `IsNameField` fields ({@link buildLabel}).\n * 4. Clusters in-browser via {@link ClusteringService.RunClustering} (client owns vectors + labels).\n * 5. Requests + applies **LLM cluster names** so clusters read semantically, not \"Cluster 1..5\".\n *\n * Inputs use the camelCase names mandated by the {@link IViewRenderer} contract (the host\n * binds them by those exact names), rather than MJ's usual PascalCase for public members.\n */\n@Component({\n standalone: false,\n selector: 'mj-cluster-view-renderer',\n encapsulation: ViewEncapsulation.None,\n template: `\n <div class=\"cluster-view-renderer\">\n @if (errorMessage) {\n <div class=\"cluster-view-message cluster-view-error\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else if (!isLoading && points.length === 0) {\n <div class=\"cluster-view-message\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n <span>No vectors available to cluster for this entity yet.</span>\n </div>\n } @else {\n <mj-cluster-scatter\n [Points]=\"points\"\n [Clusters]=\"clusters\"\n [ColorBy]=\"activeConfig.colorBy\"\n [EntityName]=\"entity?.Name ?? null\"\n [FieldPriority]=\"fieldPriority\"\n [FieldDisplayNames]=\"fieldDisplayNames\"\n [IsLoading]=\"isLoading\"\n (PointClicked)=\"onPointClicked($event)\"\n (OpenRecordRequested)=\"onOpenRecordRequested($event)\"\n >\n </mj-cluster-scatter>\n }\n </div>\n `,\n styles: [\n `\n .cluster-view-renderer {\n height: 100%;\n width: 100%;\n display: flex;\n flex-direction: column;\n }\n .cluster-view-renderer mj-cluster-scatter {\n flex: 1 1 auto;\n min-height: 0;\n }\n .cluster-view-message {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--mj-text-muted);\n padding: 32px;\n text-align: center;\n }\n .cluster-view-message i {\n font-size: 32px;\n opacity: 0.6;\n }\n .cluster-view-error {\n color: var(--mj-status-error-text);\n }\n `,\n ],\n})\nexport class ClusterViewRendererComponent extends BaseAngularComponent implements IViewRenderer<Record<string, unknown>>, OnInit, OnDestroy {\n // ---- IViewRenderer inputs (camelCase per the host contract) ----\n\n private _entity: EntityInfo | null = null;\n @Input()\n set entity(value: EntityInfo | null) {\n const changed = !UUIDsEqual(value?.ID, this._entity?.ID);\n this._entity = value;\n if (changed) {\n this.scheduleRecluster();\n }\n }\n get entity(): EntityInfo | null {\n return this._entity;\n }\n\n /** The view's records — used to map a clicked point back to its record object. */\n @Input() records: Record<string, unknown>[] = [];\n\n @Input() selectedRecordId: string | null = null;\n\n @Input() filterText: string | null = null;\n\n private _config: Record<string, unknown> = {};\n @Input()\n set config(value: Record<string, unknown>) {\n const next = toClusterViewConfig(value ?? {});\n // Only re-cluster when the clustering-relevant config actually changed. The host re-pushes the\n // (unchanged) config on unrelated events — notably when a point is clicked / selection changes —\n // and re-clustering on every push made clicking a point recompute the whole scatter. Comparing\n // the normalized config keeps clustering stable across those no-op re-pushes.\n const changed = JSON.stringify(next) !== JSON.stringify(this.activeConfig);\n this._config = value ?? {};\n this.activeConfig = next;\n if (changed) {\n this.scheduleRecluster();\n }\n }\n get config(): Record<string, unknown> {\n return this._config;\n }\n\n // ---- IViewRenderer outputs ----\n\n @Output() recordSelected = new EventEmitter<unknown>();\n @Output() recordOpened = new EventEmitter<unknown>();\n @Output() configChanged = new EventEmitter<Record<string, unknown>>();\n\n // ---- Render state ----\n\n /** The current parsed config (defaults applied). */\n public activeConfig: ClusterViewConfig = toClusterViewConfig({});\n public points: ClusterPoint[] = [];\n public clusters: ClusterInfo[] = [];\n /** Ordered field keys for prioritized display in the scatter tooltip / detail panel. */\n public fieldPriority: string[] = [];\n /** Map of field names → human-readable display names for the scatter tooltip / detail panel. */\n public fieldDisplayNames: Record<string, string> = {};\n public isLoading = false;\n public errorMessage: string | null = null;\n\n private recluster$ = new Subject<void>();\n private destroy$ = new Subject<void>();\n private clusteringService = inject(ClusteringService);\n\n /**\n * Monotonically increasing token captured at the start of each run. Because runs are async\n * and overlapping (entity/config can change mid-flight), each run compares the token it\n * captured against the latest before applying results — a stale run silently bails so it\n * cannot clobber a newer one.\n */\n private runToken = 0;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();\n }\n\n ngOnInit(): void {\n // Debounce so the entity + config inputs that arrive together on mount cause one run.\n this.recluster$.pipe(debounceTime(50), takeUntil(this.destroy$)).subscribe(() => void this.runClustering());\n // Kick an initial run AFTER the subscription exists. The component is dynamic-mounted, so the\n // host sets `entity`/`config` via setInput BEFORE ngOnInit runs — those setters fire\n // scheduleRecluster() into recluster$ while there is no subscriber yet, so the initial emission\n // is dropped (a plain Subject doesn't replay). Without this, the very first Cluster open after\n // app load renders nothing. runClustering() no-ops gracefully if no entity is set yet.\n this.scheduleRecluster();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n private scheduleRecluster(): void {\n this.recluster$.next();\n }\n\n /**\n * Run the clustering pipeline for the current entity + config and rebind the scatter.\n *\n * Mirrors the Knowledge Hub \"Visualize\" flow: resolve a deterministic Entity Document →\n * fetch its vectors with metadata → build real labels → cluster client-side → request LLM\n * cluster names. Guarded by {@link runToken} so an overlapping/stale run never clobbers a\n * newer one.\n */\n private async runClustering(): Promise<void> {\n const token = ++this.runToken;\n const entity = this._entity;\n if (!entity) {\n this.points = [];\n this.clusters = [];\n this.cdr.detectChanges();\n return;\n }\n\n this.isLoading = true;\n this.errorMessage = null;\n this.cdr.detectChanges();\n\n try {\n const gqlProvider = this.resolveGraphQLProvider();\n if (!gqlProvider) {\n this.applyError(token, 'Clustering requires a GraphQL data provider, which is not available here.');\n return;\n }\n\n const docID = await this.resolveEntityDocumentID(entity);\n if (this.isStale(token)) {\n return;\n }\n if (!docID) {\n // No active Entity Document for this entity → nothing to cluster (empty state).\n this.applyResult(token, [], []);\n return;\n }\n\n const vectors = await this.fetchVectors(gqlProvider, entity, docID);\n if (this.isStale(token)) {\n return;\n }\n if (vectors.length === 0) {\n this.applyResult(token, [], []);\n return;\n }\n\n const cfg = this.buildClusterConfig(entity, docID);\n const result = await this.clusteringService.RunClustering(vectors, cfg);\n if (this.isStale(token)) {\n return;\n }\n\n this.fieldPriority = this.computeFieldPriority(entity);\n this.applyResult(token, result.Points, result.Clusters);\n\n // Fire LLM cluster naming in the background (non-blocking) when enabled. Clusters render\n // immediately with provisional names; semantic labels appear when the LLM responds.\n if (this.activeConfig.nameClusters) {\n void this.requestClusterLabelsFromLLM(gqlProvider, token, result);\n }\n } catch (err) {\n this.applyError(token, err instanceof Error ? err.message : String(err));\n }\n }\n\n /** True when a newer run has started since `token` was captured — the caller should bail. */\n private isStale(token: number): boolean {\n return token !== this.runToken;\n }\n\n /** Apply a successful clustering result to the view (no-op if the run is stale). */\n private applyResult(token: number, points: ClusterPoint[], clusters: ClusterInfo[]): void {\n if (this.isStale(token)) {\n return;\n }\n this.points = points;\n this.clusters = clusters;\n this.isLoading = false;\n this.cdr.detectChanges();\n }\n\n /** Apply an error state to the view (no-op if the run is stale). */\n private applyError(token: number, message: string): void {\n if (this.isStale(token)) {\n return;\n }\n this.errorMessage = message;\n this.points = [];\n this.clusters = [];\n this.isLoading = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Resolve the {@link GraphQLDataProvider} backing this component's metadata provider, or\n * `null` when the active provider is not a GraphQL transport (e.g. non-browser).\n */\n private resolveGraphQLProvider(): GraphQLDataProvider | null {\n const p: IMetadataProvider = this.ProviderToUse;\n return p instanceof GraphQLDataProvider ? p : null;\n }\n\n /**\n * Resolve the entity's first active Entity Document ID **explicitly** (deterministic),\n * loading the availability engine cache if needed. Returns `null` when the entity has no\n * active document.\n */\n private async resolveEntityDocumentID(entity: EntityInfo): Promise<string | null> {\n const engine = EntityDocumentAvailabilityEngine.Instance;\n const p = this.ProviderToUse;\n await engine.Config(false, p.CurrentUser, p);\n const docs = engine.GetActiveEntityDocumentsForEntity(entity);\n return docs.length > 0 ? docs[0].ID : null;\n }\n\n /**\n * Fetch the entity document's vectors (with metadata) and build {@link ClusterInputVector}s,\n * deriving each point's display label from the entity's name fields.\n */\n private async fetchVectors(gqlProvider: GraphQLDataProvider, entity: EntityInfo, docID: string): Promise<ClusterInputVector[]> {\n const aiClient = new GraphQLAIClient(gqlProvider);\n const result = await aiClient.FetchEntityVectors({\n entityDocumentID: docID,\n maxRecords: this.activeConfig.maxRecords,\n filter: this.filterText?.trim() ? this.filterText.trim() : undefined,\n });\n\n if (!result.Success || result.Results.length === 0) {\n return [];\n }\n\n const vectors: ClusterInputVector[] = [];\n for (const item of result.Results) {\n if (!item.Values || item.Values.length === 0) {\n continue;\n }\n const metadata = this.parseVectorMetadata(item.Metadata);\n vectors.push({\n Key: item.ID,\n Label: this.buildLabel(entity, metadata),\n Vector: item.Values,\n Metadata: metadata,\n });\n }\n return vectors;\n }\n\n /** Parse the JSON metadata string stored alongside a vector into a flat string map. */\n private parseVectorMetadata(metadataJson: string): Record<string, string> {\n try {\n return JSON.parse(metadataJson) as Record<string, string>;\n } catch {\n return {};\n }\n }\n\n /**\n * Build a human-readable label for a point from the entity's name-field metadata. Combines\n * all `IsNameField` fields in Sequence order (e.g. \"Sarah Chen\" from FirstName + LastName),\n * then falls back to the single NameField, then to common heuristic keys, then to the record\n * ID — so points show real names instead of \"Unknown\".\n */\n private buildLabel(entity: EntityInfo, metadata: Record<string, string>): string {\n const nameFields = entity.Fields.filter((f) => f.IsNameField).sort((a, b) => (a.Sequence ?? 9999) - (b.Sequence ?? 9999));\n if (nameFields.length > 0) {\n const parts = nameFields.map((f) => metadata[f.Name]).filter((v) => v != null && v.trim() !== '');\n if (parts.length > 0) {\n return parts.join(' ');\n }\n }\n if (entity.NameField && metadata[entity.NameField.Name]) {\n return metadata[entity.NameField.Name];\n }\n return metadata['Name'] || metadata['Title'] || metadata['Description']?.substring(0, 60) || metadata['RecordID'] || 'Unknown';\n }\n\n /**\n * Compute the prioritized field order + display names for the scatter tooltip / detail panel.\n * Orders: name fields first, then default-in-view fields, then the rest by Sequence; skips\n * internal/virtual/primary-key fields. Also populates {@link fieldDisplayNames}.\n */\n private computeFieldPriority(entity: EntityInfo): string[] {\n const internalKeys = new Set(['ID', 'Entity', 'EntityIcon', 'RecordID', 'TemplateID', '__mj_UpdatedAt', '__mj_CreatedAt']);\n\n const displayNames: Record<string, string> = {};\n for (const f of entity.Fields) {\n displayNames[f.Name] = f.DisplayNameOrName;\n }\n this.fieldDisplayNames = displayNames;\n\n return entity.Fields.filter((f) => !internalKeys.has(f.Name) && !f.IsVirtual && !f.IsPrimaryKey)\n .sort((a, b) => {\n if (a.IsNameField !== b.IsNameField) return a.IsNameField ? -1 : 1;\n if (a.DefaultInView !== b.DefaultInView) return a.DefaultInView ? -1 : 1;\n return (a.Sequence ?? 9999) - (b.Sequence ?? 9999);\n })\n .map((f) => f.Name);\n }\n\n /** Build the engine {@link ClusterConfig} from the entity + the view-type config. */\n private buildClusterConfig(entity: EntityInfo, docID: string): ClusterConfig {\n const c = this.activeConfig;\n return {\n ...DefaultClusterConfig(),\n EntityName: entity.Name,\n EntityDocumentID: docID,\n ColorBy: c.colorBy,\n Dimensions: c.dimensions,\n Algorithm: c.algorithm,\n K: c.k,\n MaxRecords: c.maxRecords,\n Filter: this.filterText?.trim() ?? '',\n };\n }\n\n // ================================================================\n // LLM cluster naming\n // ================================================================\n\n /**\n * After clustering completes, send sample records per cluster to the \"Cluster Naming\" AI\n * prompt and apply the returned semantic labels to {@link clusters}. Best-effort: failures\n * are logged and leave the provisional \"Cluster N\" labels in place. No-op if the run became\n * stale while the LLM call was in flight.\n */\n private async requestClusterLabelsFromLLM(gqlProvider: GraphQLDataProvider, token: number, result: ClusterVisualizationResult): Promise<void> {\n try {\n const clusterData = this.buildClusterDataForPrompt(result);\n if (!clusterData) {\n return;\n }\n\n // AIEngineBase is deferred at startup; ensure it's loaded before reading .Prompts.\n await AIEngineBase.Instance.EnsureLoaded();\n const promptEntity = AIEngineBase.Instance.Prompts.find((p) => p.Name === 'Cluster Naming');\n if (!promptEntity) {\n console.warn('[ClusterViewRenderer] \"Cluster Naming\" prompt not found — run metadata sync');\n return;\n }\n\n const aiClient = new GraphQLAIClient(gqlProvider);\n const promptResult = await aiClient.RunAIPrompt({\n promptId: promptEntity.ID,\n data: { clusterData },\n });\n\n if (this.isStale(token) || !promptResult.success || !promptResult.parsedResult) {\n return;\n }\n\n const labels = promptResult.parsedResult as Array<{ clusterId: number; label: string }>;\n const clusterLabels: ClusterLabel[] = labels.map((l) => ({\n ClusterId: l.clusterId,\n Label: l.label,\n IsUserEdited: false,\n }));\n this.applyLabelsToClusters(clusterLabels);\n this.cdr.detectChanges();\n } catch (error) {\n console.warn('[ClusterViewRenderer] LLM cluster naming failed:', error);\n }\n }\n\n /** Build a text block describing sample records per cluster for the LLM naming prompt. */\n private buildClusterDataForPrompt(result: ClusterVisualizationResult): string | null {\n if (!result.Clusters || result.Clusters.length === 0) {\n return null;\n }\n\n const lines: string[] = [];\n for (const cluster of result.Clusters) {\n const clusterPoints = result.Points.filter((p) => p.ClusterId === cluster.Id);\n const samples = clusterPoints.slice(0, 5);\n if (samples.length === 0) {\n continue;\n }\n\n lines.push(`### Cluster ${cluster.Id} (${clusterPoints.length} records)`);\n for (const point of samples) {\n const meta = (point.Metadata ?? {}) as Record<string, unknown>;\n const fields = ['Name', 'Title', 'Description', 'Entity', 'Status', 'Type']\n .filter((f) => meta[f])\n .map((f) => `${f}: ${meta[f]}`)\n .join(', ');\n lines.push(`- ${fields || point.Label}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n\n /** Apply LLM-generated labels onto the live {@link clusters} array by cluster ID. */\n private applyLabelsToClusters(labels: ClusterLabel[]): void {\n for (const label of labels) {\n const cluster = this.clusters.find((c) => c.Id === label.ClusterId);\n if (cluster) {\n cluster.Label = label.Label;\n }\n }\n }\n\n // ================================================================\n // Point → record mapping\n // ================================================================\n\n onPointClicked(point: ClusterPoint): void {\n this.recordSelected.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });\n }\n\n onOpenRecordRequested(point: ClusterPoint): void {\n this.recordOpened.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });\n }\n\n /**\n * Resolve the host record corresponding to a scatter point. The vector key is the Entity\n * Record Document ID, so match on the point metadata's `RecordID` (the underlying entity\n * record's primary key) first, then fall back to a direct key match.\n */\n private recordForPoint(point: ClusterPoint): Record<string, unknown> | null {\n const meta = (point.Metadata ?? {}) as Record<string, unknown>;\n const recordID = typeof meta['RecordID'] === 'string' ? (meta['RecordID'] as string) : null;\n const key = recordID ?? point.VectorKey;\n if (!key) {\n return null;\n }\n return (\n this.records.find((r) => {\n const id = r['ID'];\n return typeof id === 'string' && UUIDsEqual(id, key);\n }) ?? null\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cluster-view-renderer.component.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view-renderer.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ChangeDetectorRef, OnInit, OnDestroy, ViewEncapsulation, inject } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { debounceTime, takeUntil } from 'rxjs/operators';\nimport { EntityInfo, IMetadataProvider } from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { GraphQLDataProvider, GraphQLAIClient } from '@memberjunction/graphql-dataprovider';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { IViewRenderer } from '@memberjunction/ng-entity-viewer';\nimport { ClusteringService } from '../clustering.service';\nimport {\n ClusterConfig,\n ClusterPoint,\n ClusterInfo,\n ClusterInputVector,\n ClusterLabel,\n ClusterVisualizationResult,\n DefaultClusterConfig,\n} from '../clustering.types';\nimport { ClusterViewConfig, ToClusterViewConfig } from './cluster-view.types';\nimport { EntityDocumentAvailabilityEngine } from './entity-document-availability.engine';\n\n/**\n * ClusterViewRendererComponent\n * ----------------------------\n * The Cluster **view type** renderer — a thin {@link IViewRenderer} adapter that lets any\n * entity with vectors be visualized as a cluster scatter directly inside `mj-entity-viewer`,\n * reusing the exact same {@link ClusteringService} + `mj-cluster-scatter` the Knowledge Hub\n * \"Visualize\" cluster surface uses.\n *\n * The host feeds it the standard renderer inputs (entity / records / filterText / config) via\n * `setInput`, and listens for `recordSelected` / `recordOpened`. When the entity or config\n * changes it re-runs the clustering pipeline and rebinds the scatter.\n *\n * **Why this mirrors the Knowledge Hub resource rather than the server `RunClusterAnalysis`\n * path:** the server analysis returns points keyed only by record ID (so labels render as\n * \"Unknown\"), produces generic \"Cluster N\" names, and picks an Entity Document\n * non-deterministically when none is supplied (causing flip-flop \"no vectors\" results). This\n * renderer instead:\n * 1. Resolves the entity's **first active Entity Document explicitly** (deterministic source).\n * 2. Fetches that document's vectors (with their JSON metadata) via {@link GraphQLAIClient.FetchEntityVectors}.\n * 3. Builds real point labels from the entity's `IsNameField` fields ({@link buildLabel}).\n * 4. Clusters in-browser via {@link ClusteringService.RunClustering} (client owns vectors + labels).\n * 5. Requests + applies **LLM cluster names** so clusters read semantically, not \"Cluster 1..5\".\n *\n * Inputs use the camelCase names mandated by the {@link IViewRenderer} contract (the host\n * binds them by those exact names), rather than MJ's usual PascalCase for public members.\n */\n@Component({\n standalone: false,\n selector: 'mj-cluster-view-renderer',\n encapsulation: ViewEncapsulation.None,\n template: `\n <div class=\"cluster-view-renderer\">\n @if (errorMessage) {\n <div class=\"cluster-view-message cluster-view-error\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <span>{{ errorMessage }}</span>\n </div>\n } @else if (!isLoading && points.length === 0) {\n <div class=\"cluster-view-message\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n <span>No vectors available to cluster for this entity yet.</span>\n </div>\n } @else {\n <mj-cluster-scatter\n [Points]=\"points\"\n [Clusters]=\"clusters\"\n [ColorBy]=\"activeConfig.colorBy\"\n [EntityName]=\"entity?.Name ?? null\"\n [FieldPriority]=\"fieldPriority\"\n [FieldDisplayNames]=\"fieldDisplayNames\"\n [IsLoading]=\"isLoading\"\n (PointClicked)=\"onPointClicked($event)\"\n (OpenRecordRequested)=\"onOpenRecordRequested($event)\"\n >\n </mj-cluster-scatter>\n }\n </div>\n `,\n styles: [\n `\n .cluster-view-renderer {\n height: 100%;\n width: 100%;\n display: flex;\n flex-direction: column;\n }\n .cluster-view-renderer mj-cluster-scatter {\n flex: 1 1 auto;\n min-height: 0;\n }\n .cluster-view-message {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--mj-text-muted);\n padding: 32px;\n text-align: center;\n }\n .cluster-view-message i {\n font-size: 32px;\n opacity: 0.6;\n }\n .cluster-view-error {\n color: var(--mj-status-error-text);\n }\n `,\n ],\n})\nexport class ClusterViewRendererComponent extends BaseAngularComponent implements IViewRenderer<Record<string, unknown>>, OnInit, OnDestroy {\n // ---- IViewRenderer inputs (camelCase per the host contract) ----\n\n private _entity: EntityInfo | null = null;\n @Input()\n set entity(value: EntityInfo | null) {\n const changed = !UUIDsEqual(value?.ID, this._entity?.ID);\n this._entity = value;\n if (changed) {\n this.scheduleRecluster();\n }\n }\n get entity(): EntityInfo | null {\n return this._entity;\n }\n\n /** The view's records — used to map a clicked point back to its record object. */\n @Input() records: Record<string, unknown>[] = [];\n\n @Input() selectedRecordId: string | null = null;\n\n @Input() filterText: string | null = null;\n\n private _config: Record<string, unknown> = {};\n @Input()\n set config(value: Record<string, unknown>) {\n const next = ToClusterViewConfig(value ?? {});\n // Only re-cluster when the clustering-relevant config actually changed. The host re-pushes the\n // (unchanged) config on unrelated events — notably when a point is clicked / selection changes —\n // and re-clustering on every push made clicking a point recompute the whole scatter. Comparing\n // the normalized config keeps clustering stable across those no-op re-pushes.\n const changed = JSON.stringify(next) !== JSON.stringify(this.ActiveConfig);\n this._config = value ?? {};\n this.ActiveConfig = next;\n if (changed) {\n this.scheduleRecluster();\n }\n }\n get config(): Record<string, unknown> {\n return this._config;\n }\n\n // ---- IViewRenderer outputs ----\n\n @Output() recordSelected = new EventEmitter<unknown>();\n @Output() recordOpened = new EventEmitter<unknown>();\n @Output() configChanged = new EventEmitter<Record<string, unknown>>();\n\n // ---- Render state ----\n\n /** The current parsed config (defaults applied). */\n public ActiveConfig: ClusterViewConfig = ToClusterViewConfig({});\n\n /** @deprecated Use {@link ActiveConfig}. */\n public get activeConfig(): ClusterViewConfig {\n return this.ActiveConfig;\n }\n /** @deprecated Use {@link ActiveConfig}. */\n public set activeConfig(value: ClusterViewConfig) {\n this.ActiveConfig = value;\n }\n public Points: ClusterPoint[] = [];\n\n /** @deprecated Use {@link Points}. */\n public get points(): ClusterPoint[] {\n return this.Points;\n }\n /** @deprecated Use {@link Points}. */\n public set points(value: ClusterPoint[]) {\n this.Points = value;\n }\n public Clusters: ClusterInfo[] = [];\n\n /** @deprecated Use {@link Clusters}. */\n public get clusters(): ClusterInfo[] {\n return this.Clusters;\n }\n /** @deprecated Use {@link Clusters}. */\n public set clusters(value: ClusterInfo[]) {\n this.Clusters = value;\n }\n /** Ordered field keys for prioritized display in the scatter tooltip / detail panel. */\n public FieldPriority: string[] = [];\n\n /** @deprecated Use {@link FieldPriority}. */\n public get fieldPriority(): string[] {\n return this.FieldPriority;\n }\n /** @deprecated Use {@link FieldPriority}. */\n public set fieldPriority(value: string[]) {\n this.FieldPriority = value;\n }\n /** Map of field names → human-readable display names for the scatter tooltip / detail panel. */\n public FieldDisplayNames: Record<string, string> = {};\n\n /** @deprecated Use {@link FieldDisplayNames}. */\n public get fieldDisplayNames(): Record<string, string> {\n return this.FieldDisplayNames;\n }\n /** @deprecated Use {@link FieldDisplayNames}. */\n public set fieldDisplayNames(value: Record<string, string>) {\n this.FieldDisplayNames = value;\n }\n public isLoading = false;\n public errorMessage: string | null = null;\n\n private recluster$ = new Subject<void>();\n private destroy$ = new Subject<void>();\n private clusteringService = inject(ClusteringService);\n\n /**\n * Monotonically increasing token captured at the start of each run. Because runs are async\n * and overlapping (entity/config can change mid-flight), each run compares the token it\n * captured against the latest before applying results — a stale run silently bails so it\n * cannot clobber a newer one.\n */\n private runToken = 0;\n\n constructor(private cdr: ChangeDetectorRef) {\n super();\n }\n\n ngOnInit(): void {\n // Debounce so the entity + config inputs that arrive together on mount cause one run.\n this.recluster$.pipe(debounceTime(50), takeUntil(this.destroy$)).subscribe(() => void this.runClustering());\n // Kick an initial run AFTER the subscription exists. The component is dynamic-mounted, so the\n // host sets `entity`/`config` via setInput BEFORE ngOnInit runs — those setters fire\n // scheduleRecluster() into recluster$ while there is no subscriber yet, so the initial emission\n // is dropped (a plain Subject doesn't replay). Without this, the very first Cluster open after\n // app load renders nothing. runClustering() no-ops gracefully if no entity is set yet.\n this.scheduleRecluster();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n private scheduleRecluster(): void {\n this.recluster$.next();\n }\n\n /**\n * Run the clustering pipeline for the current entity + config and rebind the scatter.\n *\n * Mirrors the Knowledge Hub \"Visualize\" flow: resolve a deterministic Entity Document →\n * fetch its vectors with metadata → build real labels → cluster client-side → request LLM\n * cluster names. Guarded by {@link runToken} so an overlapping/stale run never clobbers a\n * newer one.\n */\n private async runClustering(): Promise<void> {\n const token = ++this.runToken;\n const entity = this._entity;\n if (!entity) {\n this.Points = [];\n this.Clusters = [];\n this.cdr.detectChanges();\n return;\n }\n\n this.isLoading = true;\n this.errorMessage = null;\n this.cdr.detectChanges();\n\n try {\n const gqlProvider = this.resolveGraphQLProvider();\n if (!gqlProvider) {\n this.applyError(token, 'Clustering requires a GraphQL data provider, which is not available here.');\n return;\n }\n\n const docID = await this.resolveEntityDocumentID(entity);\n if (this.isStale(token)) {\n return;\n }\n if (!docID) {\n // No active Entity Document for this entity → nothing to cluster (empty state).\n this.applyResult(token, [], []);\n return;\n }\n\n const vectors = await this.fetchVectors(gqlProvider, entity, docID);\n if (this.isStale(token)) {\n return;\n }\n if (vectors.length === 0) {\n this.applyResult(token, [], []);\n return;\n }\n\n const cfg = this.buildClusterConfig(entity, docID);\n const result = await this.clusteringService.RunClustering(vectors, cfg);\n if (this.isStale(token)) {\n return;\n }\n\n this.FieldPriority = this.computeFieldPriority(entity);\n this.applyResult(token, result.Points, result.Clusters);\n\n // Fire LLM cluster naming in the background (non-blocking) when enabled. Clusters render\n // immediately with provisional names; semantic labels appear when the LLM responds.\n if (this.ActiveConfig.nameClusters) {\n void this.requestClusterLabelsFromLLM(gqlProvider, token, result);\n }\n } catch (err) {\n this.applyError(token, err instanceof Error ? err.message : String(err));\n }\n }\n\n /** True when a newer run has started since `token` was captured — the caller should bail. */\n private isStale(token: number): boolean {\n return token !== this.runToken;\n }\n\n /** Apply a successful clustering result to the view (no-op if the run is stale). */\n private applyResult(token: number, points: ClusterPoint[], clusters: ClusterInfo[]): void {\n if (this.isStale(token)) {\n return;\n }\n this.Points = points;\n this.Clusters = clusters;\n this.isLoading = false;\n this.cdr.detectChanges();\n }\n\n /** Apply an error state to the view (no-op if the run is stale). */\n private applyError(token: number, message: string): void {\n if (this.isStale(token)) {\n return;\n }\n this.errorMessage = message;\n this.Points = [];\n this.Clusters = [];\n this.isLoading = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Resolve the {@link GraphQLDataProvider} backing this component's metadata provider, or\n * `null` when the active provider is not a GraphQL transport (e.g. non-browser).\n */\n private resolveGraphQLProvider(): GraphQLDataProvider | null {\n const p: IMetadataProvider = this.ProviderToUse;\n return p instanceof GraphQLDataProvider ? p : null;\n }\n\n /**\n * Resolve the entity's first active Entity Document ID **explicitly** (deterministic),\n * loading the availability engine cache if needed. Returns `null` when the entity has no\n * active document.\n */\n private async resolveEntityDocumentID(entity: EntityInfo): Promise<string | null> {\n const engine = EntityDocumentAvailabilityEngine.Instance;\n const p = this.ProviderToUse;\n await engine.Config(false, p.CurrentUser, p);\n const docs = engine.GetActiveEntityDocumentsForEntity(entity);\n return docs.length > 0 ? docs[0].ID : null;\n }\n\n /**\n * Fetch the entity document's vectors (with metadata) and build {@link ClusterInputVector}s,\n * deriving each point's display label from the entity's name fields.\n */\n private async fetchVectors(gqlProvider: GraphQLDataProvider, entity: EntityInfo, docID: string): Promise<ClusterInputVector[]> {\n const aiClient = new GraphQLAIClient(gqlProvider);\n const result = await aiClient.FetchEntityVectors({\n entityDocumentID: docID,\n maxRecords: this.ActiveConfig.maxRecords,\n filter: this.filterText?.trim() ? this.filterText.trim() : undefined,\n });\n\n if (!result.Success || result.Results.length === 0) {\n return [];\n }\n\n const vectors: ClusterInputVector[] = [];\n for (const item of result.Results) {\n if (!item.Values || item.Values.length === 0) {\n continue;\n }\n const metadata = this.parseVectorMetadata(item.Metadata);\n vectors.push({\n Key: item.ID,\n Label: this.buildLabel(entity, metadata),\n Vector: item.Values,\n Metadata: metadata,\n });\n }\n return vectors;\n }\n\n /** Parse the JSON metadata string stored alongside a vector into a flat string map. */\n private parseVectorMetadata(metadataJson: string): Record<string, string> {\n try {\n return JSON.parse(metadataJson) as Record<string, string>;\n } catch {\n return {};\n }\n }\n\n /**\n * Build a human-readable label for a point from the entity's name-field metadata. Combines\n * all `IsNameField` fields in Sequence order (e.g. \"Sarah Chen\" from FirstName + LastName),\n * then falls back to the single NameField, then to common heuristic keys, then to the record\n * ID — so points show real names instead of \"Unknown\".\n */\n private buildLabel(entity: EntityInfo, metadata: Record<string, string>): string {\n const nameFields = entity.Fields.filter((f) => f.IsNameField).sort((a, b) => (a.Sequence ?? 9999) - (b.Sequence ?? 9999));\n if (nameFields.length > 0) {\n const parts = nameFields.map((f) => metadata[f.Name]).filter((v) => v != null && v.trim() !== '');\n if (parts.length > 0) {\n return parts.join(' ');\n }\n }\n if (entity.NameField && metadata[entity.NameField.Name]) {\n return metadata[entity.NameField.Name];\n }\n return metadata['Name'] || metadata['Title'] || metadata['Description']?.substring(0, 60) || metadata['RecordID'] || 'Unknown';\n }\n\n /**\n * Compute the prioritized field order + display names for the scatter tooltip / detail panel.\n * Orders: name fields first, then default-in-view fields, then the rest by Sequence; skips\n * internal/virtual/primary-key fields. Also populates {@link fieldDisplayNames}.\n */\n private computeFieldPriority(entity: EntityInfo): string[] {\n const internalKeys = new Set(['ID', 'Entity', 'EntityIcon', 'RecordID', 'TemplateID', '__mj_UpdatedAt', '__mj_CreatedAt']);\n\n const displayNames: Record<string, string> = {};\n for (const f of entity.Fields) {\n displayNames[f.Name] = f.DisplayNameOrName;\n }\n this.FieldDisplayNames = displayNames;\n\n return entity.Fields.filter((f) => !internalKeys.has(f.Name) && !f.IsVirtual && !f.IsPrimaryKey)\n .sort((a, b) => {\n if (a.IsNameField !== b.IsNameField) return a.IsNameField ? -1 : 1;\n if (a.DefaultInView !== b.DefaultInView) return a.DefaultInView ? -1 : 1;\n return (a.Sequence ?? 9999) - (b.Sequence ?? 9999);\n })\n .map((f) => f.Name);\n }\n\n /** Build the engine {@link ClusterConfig} from the entity + the view-type config. */\n private buildClusterConfig(entity: EntityInfo, docID: string): ClusterConfig {\n const c = this.ActiveConfig;\n return {\n ...DefaultClusterConfig(),\n EntityName: entity.Name,\n EntityDocumentID: docID,\n ColorBy: c.colorBy,\n Dimensions: c.dimensions,\n Algorithm: c.algorithm,\n K: c.k,\n MaxRecords: c.maxRecords,\n Filter: this.filterText?.trim() ?? '',\n };\n }\n\n // ================================================================\n // LLM cluster naming\n // ================================================================\n\n /**\n * After clustering completes, send sample records per cluster to the \"Cluster Naming\" AI\n * prompt and apply the returned semantic labels to {@link clusters}. Best-effort: failures\n * are logged and leave the provisional \"Cluster N\" labels in place. No-op if the run became\n * stale while the LLM call was in flight.\n */\n private async requestClusterLabelsFromLLM(gqlProvider: GraphQLDataProvider, token: number, result: ClusterVisualizationResult): Promise<void> {\n try {\n const clusterData = this.buildClusterDataForPrompt(result);\n if (!clusterData) {\n return;\n }\n\n // AIEngineBase is deferred at startup; ensure it's loaded before reading .Prompts.\n await AIEngineBase.Instance.EnsureLoaded();\n const promptEntity = AIEngineBase.Instance.Prompts.find((p) => p.Name === 'Cluster Naming');\n if (!promptEntity) {\n console.warn('[ClusterViewRenderer] \"Cluster Naming\" prompt not found — run metadata sync');\n return;\n }\n\n const aiClient = new GraphQLAIClient(gqlProvider);\n const promptResult = await aiClient.RunAIPrompt({\n promptId: promptEntity.ID,\n data: { clusterData },\n });\n\n if (this.isStale(token) || !promptResult.success || !promptResult.parsedResult) {\n return;\n }\n\n const labels = promptResult.parsedResult as Array<{ clusterId: number; label: string }>;\n const clusterLabels: ClusterLabel[] = labels.map((l) => ({\n ClusterId: l.clusterId,\n Label: l.label,\n IsUserEdited: false,\n }));\n this.applyLabelsToClusters(clusterLabels);\n this.cdr.detectChanges();\n } catch (error) {\n console.warn('[ClusterViewRenderer] LLM cluster naming failed:', error);\n }\n }\n\n /** Build a text block describing sample records per cluster for the LLM naming prompt. */\n private buildClusterDataForPrompt(result: ClusterVisualizationResult): string | null {\n if (!result.Clusters || result.Clusters.length === 0) {\n return null;\n }\n\n const lines: string[] = [];\n for (const cluster of result.Clusters) {\n const clusterPoints = result.Points.filter((p) => p.ClusterId === cluster.Id);\n const samples = clusterPoints.slice(0, 5);\n if (samples.length === 0) {\n continue;\n }\n\n lines.push(`### Cluster ${cluster.Id} (${clusterPoints.length} records)`);\n for (const point of samples) {\n const meta = (point.Metadata ?? {}) as Record<string, unknown>;\n const fields = ['Name', 'Title', 'Description', 'Entity', 'Status', 'Type']\n .filter((f) => meta[f])\n .map((f) => `${f}: ${meta[f]}`)\n .join(', ');\n lines.push(`- ${fields || point.Label}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n\n /** Apply LLM-generated labels onto the live {@link clusters} array by cluster ID. */\n private applyLabelsToClusters(labels: ClusterLabel[]): void {\n for (const label of labels) {\n const cluster = this.Clusters.find((c) => c.Id === label.ClusterId);\n if (cluster) {\n cluster.Label = label.Label;\n }\n }\n }\n\n // ================================================================\n // Point → record mapping\n // ================================================================\n\n OnPointClicked(point: ClusterPoint): void {\n this.recordSelected.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });\n }\n\n /** @deprecated Use {@link OnPointClicked}. */\n onPointClicked(point: ClusterPoint): void {\n return this.OnPointClicked(point);\n }\n\n OnOpenRecordRequested(point: ClusterPoint): void {\n this.recordOpened.emit(this.recordForPoint(point) ?? { ID: point.VectorKey });\n }\n\n /** @deprecated Use {@link OnOpenRecordRequested}. */\n onOpenRecordRequested(point: ClusterPoint): void {\n return this.OnOpenRecordRequested(point);\n }\n\n /**\n * Resolve the host record corresponding to a scatter point. The vector key is the Entity\n * Record Document ID, so match on the point metadata's `RecordID` (the underlying entity\n * record's primary key) first, then fall back to a direct key match.\n */\n private recordForPoint(point: ClusterPoint): Record<string, unknown> | null {\n const meta = (point.Metadata ?? {}) as Record<string, unknown>;\n const recordID = typeof meta['RecordID'] === 'string' ? (meta['RecordID'] as string) : null;\n const key = recordID ?? point.VectorKey;\n if (!key) {\n return null;\n }\n return (\n this.records.find((r) => {\n const id = r['ID'];\n return typeof id === 'string' && UUIDsEqual(id, key);\n }) ?? null\n );\n }\n}\n"]}
|
|
@@ -27,5 +27,7 @@ export declare const DEFAULT_CLUSTER_VIEW_CONFIG: ClusterViewConfig;
|
|
|
27
27
|
* filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or
|
|
28
28
|
* empty input (e.g. a brand-new view with no saved config).
|
|
29
29
|
*/
|
|
30
|
+
export declare function ToClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig;
|
|
31
|
+
/** @deprecated Use {@link ToClusterViewConfig}. */
|
|
30
32
|
export declare function toClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig;
|
|
31
33
|
//# sourceMappingURL=cluster-view.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view.types.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAClB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,kDAAkD;AAClD,eAAO,MAAM,2BAA2B,EAAE,iBAOzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAatG"}
|
|
1
|
+
{"version":3,"file":"cluster-view.types.d.ts","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,2DAA2D;IAC3D,CAAC,EAAE,MAAM,CAAC;IACV,iEAAiE;IACjE,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAClB,4EAA4E;IAC5E,OAAO,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC9B,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,kDAAkD;AAClD,eAAO,MAAM,2BAA2B,EAAE,iBAOzC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAatG;AAED,mDAAmD;AACnD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAEtG"}
|
|
@@ -12,7 +12,7 @@ export const DEFAULT_CLUSTER_VIEW_CONFIG = {
|
|
|
12
12
|
* filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or
|
|
13
13
|
* empty input (e.g. a brand-new view with no saved config).
|
|
14
14
|
*/
|
|
15
|
-
export function
|
|
15
|
+
export function ToClusterViewConfig(raw) {
|
|
16
16
|
const r = raw ?? {};
|
|
17
17
|
const algorithm = r['algorithm'] === 'dbscan' ? 'dbscan' : 'kmeans';
|
|
18
18
|
const dimensions = r['dimensions'] === 3 ? 3 : 2;
|
|
@@ -26,4 +26,8 @@ export function toClusterViewConfig(raw) {
|
|
|
26
26
|
nameClusters: typeof r['nameClusters'] === 'boolean' ? r['nameClusters'] : DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/** @deprecated Use {@link ToClusterViewConfig}. */
|
|
30
|
+
export function toClusterViewConfig(raw) {
|
|
31
|
+
return ToClusterViewConfig(raw);
|
|
32
|
+
}
|
|
29
33
|
//# sourceMappingURL=cluster-view.types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster-view.types.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAwBA,kDAAkD;AAClD,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC5D,SAAS,EAAE,QAAQ;IACnB,CAAC,EAAE,CAAC;IACJ,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,IAAI;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA+C;IACjF,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;IACpB,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,OAAO;QACL,SAAS;QACT,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC;QAClF,UAAU;QACV,OAAO;QACP,UAAU,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,UAAU;QACtH,YAAY,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,cAAc,CAAa,CAAC,CAAC,CAAC,2BAA2B,CAAC,YAAY;KACjI,CAAC;AACJ,CAAC","sourcesContent":["import { ClusterAlgorithm } from '../clustering.types';\n\n/**\n * Configuration for the Cluster **view type** (the entity-viewer plug-in), persisted per\n * `UserView` in `DisplayState.viewTypeConfigs` keyed by the Cluster `MJ: View Types` row ID.\n *\n * This is the plug-in-owned config shape referenced opaquely by the host as\n * `Record<string, unknown>`; the Cluster renderer + prop-sheet interpret it with this type.\n */\nexport interface ClusterViewConfig {\n /** Clustering algorithm. */\n algorithm: ClusterAlgorithm;\n /** Number of clusters for K-Means (ignored for DBSCAN). */\n k: number;\n /** Projected layout dimensionality: 2 (SVG) or 3 (rotatable). */\n dimensions: 2 | 3;\n /** Legend mode: color by assigned cluster (default) or by source entity. */\n colorBy: 'cluster' | 'entity';\n /** Maximum records to fetch for clustering. */\n maxRecords: number;\n /** Whether to name clusters with an LLM (server-side). */\n nameClusters: boolean;\n}\n\n/** Sensible defaults for a fresh Cluster view. */\nexport const DEFAULT_CLUSTER_VIEW_CONFIG: ClusterViewConfig = {\n algorithm: 'kmeans',\n k: 5,\n dimensions: 2,\n colorBy: 'cluster',\n maxRecords: 500,\n nameClusters: true,\n};\n\n/**\n * Normalizes an opaque host config map into a fully-populated {@link ClusterViewConfig},\n * filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or\n * empty input (e.g. a brand-new view with no saved config).\n */\nexport function
|
|
1
|
+
{"version":3,"file":"cluster-view.types.js","sourceRoot":"","sources":["../../../src/lib/view-type/cluster-view.types.ts"],"names":[],"mappings":"AAwBA,kDAAkD;AAClD,MAAM,CAAC,MAAM,2BAA2B,GAAsB;IAC5D,SAAS,EAAE,QAAQ;IACnB,CAAC,EAAE,CAAC;IACJ,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,IAAI;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA+C;IACjF,MAAM,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;IACpB,MAAM,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,OAAO;QACL,SAAS;QACT,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC;QAClF,UAAU;QACV,OAAO;QACP,UAAU,EAAE,OAAO,CAAC,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAY,CAAC,CAAC,CAAC,2BAA2B,CAAC,UAAU;QACtH,YAAY,EAAE,OAAO,CAAC,CAAC,cAAc,CAAC,KAAK,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,cAAc,CAAa,CAAC,CAAC,CAAC,2BAA2B,CAAC,YAAY;KACjI,CAAC;AACJ,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,mBAAmB,CAAC,GAA+C;IACjF,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC","sourcesContent":["import { ClusterAlgorithm } from '../clustering.types';\n\n/**\n * Configuration for the Cluster **view type** (the entity-viewer plug-in), persisted per\n * `UserView` in `DisplayState.viewTypeConfigs` keyed by the Cluster `MJ: View Types` row ID.\n *\n * This is the plug-in-owned config shape referenced opaquely by the host as\n * `Record<string, unknown>`; the Cluster renderer + prop-sheet interpret it with this type.\n */\nexport interface ClusterViewConfig {\n /** Clustering algorithm. */\n algorithm: ClusterAlgorithm;\n /** Number of clusters for K-Means (ignored for DBSCAN). */\n k: number;\n /** Projected layout dimensionality: 2 (SVG) or 3 (rotatable). */\n dimensions: 2 | 3;\n /** Legend mode: color by assigned cluster (default) or by source entity. */\n colorBy: 'cluster' | 'entity';\n /** Maximum records to fetch for clustering. */\n maxRecords: number;\n /** Whether to name clusters with an LLM (server-side). */\n nameClusters: boolean;\n}\n\n/** Sensible defaults for a fresh Cluster view. */\nexport const DEFAULT_CLUSTER_VIEW_CONFIG: ClusterViewConfig = {\n algorithm: 'kmeans',\n k: 5,\n dimensions: 2,\n colorBy: 'cluster',\n maxRecords: 500,\n nameClusters: true,\n};\n\n/**\n * Normalizes an opaque host config map into a fully-populated {@link ClusterViewConfig},\n * filling any missing keys from {@link DEFAULT_CLUSTER_VIEW_CONFIG}. Tolerant of partial or\n * empty input (e.g. a brand-new view with no saved config).\n */\nexport function ToClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig {\n const r = raw ?? {};\n const algorithm = r['algorithm'] === 'dbscan' ? 'dbscan' : 'kmeans';\n const dimensions = r['dimensions'] === 3 ? 3 : 2;\n const colorBy = r['colorBy'] === 'entity' ? 'entity' : 'cluster';\n return {\n algorithm,\n k: typeof r['k'] === 'number' ? (r['k'] as number) : DEFAULT_CLUSTER_VIEW_CONFIG.k,\n dimensions,\n colorBy,\n maxRecords: typeof r['maxRecords'] === 'number' ? (r['maxRecords'] as number) : DEFAULT_CLUSTER_VIEW_CONFIG.maxRecords,\n nameClusters: typeof r['nameClusters'] === 'boolean' ? (r['nameClusters'] as boolean) : DEFAULT_CLUSTER_VIEW_CONFIG.nameClusters,\n };\n}\n\n/** @deprecated Use {@link ToClusterViewConfig}. */\nexport function toClusterViewConfig(raw: Record<string, unknown> | null | undefined): ClusterViewConfig {\n return ToClusterViewConfig(raw);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-clustering",
|
|
3
|
-
"version": "6.2.0-edge.
|
|
3
|
+
"version": "6.2.0-edge.2",
|
|
4
4
|
"description": "MemberJunction: Reusable Angular clustering visualization components with scatter plot, config panel, and clustering service",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@angular/compiler": "21.2.22",
|
|
21
21
|
"@angular/compiler-cli": "21.2.22",
|
|
22
|
-
"@memberjunction/ng-test-utils": "6.2.0-edge.
|
|
22
|
+
"@memberjunction/ng-test-utils": "6.2.0-edge.2",
|
|
23
23
|
"vitest": "^4.0.18"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"@angular/forms": "^21.2.22"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@memberjunction/ng-ui-components": "6.2.0-edge.
|
|
32
|
-
"@memberjunction/ai-engine-base": "6.2.0-edge.
|
|
33
|
-
"@memberjunction/ai-vectors-memory": "6.2.0-edge.
|
|
34
|
-
"@memberjunction/core": "6.2.0-edge.
|
|
35
|
-
"@memberjunction/core-entities": "6.2.0-edge.
|
|
36
|
-
"@memberjunction/global": "6.2.0-edge.
|
|
37
|
-
"@memberjunction/graphql-dataprovider": "6.2.0-edge.
|
|
38
|
-
"@memberjunction/ng-base-types": "6.2.0-edge.
|
|
39
|
-
"@memberjunction/ng-entity-card": "6.2.0-edge.
|
|
40
|
-
"@memberjunction/ng-entity-viewer": "6.2.0-edge.
|
|
31
|
+
"@memberjunction/ng-ui-components": "6.2.0-edge.2",
|
|
32
|
+
"@memberjunction/ai-engine-base": "6.2.0-edge.2",
|
|
33
|
+
"@memberjunction/ai-vectors-memory": "6.2.0-edge.2",
|
|
34
|
+
"@memberjunction/core": "6.2.0-edge.2",
|
|
35
|
+
"@memberjunction/core-entities": "6.2.0-edge.2",
|
|
36
|
+
"@memberjunction/global": "6.2.0-edge.2",
|
|
37
|
+
"@memberjunction/graphql-dataprovider": "6.2.0-edge.2",
|
|
38
|
+
"@memberjunction/ng-base-types": "6.2.0-edge.2",
|
|
39
|
+
"@memberjunction/ng-entity-card": "6.2.0-edge.2",
|
|
40
|
+
"@memberjunction/ng-entity-viewer": "6.2.0-edge.2",
|
|
41
41
|
"rxjs": "^7.8.2",
|
|
42
42
|
"tslib": "^2.8.1",
|
|
43
43
|
"umap-js": "^1.4.0"
|