@memberjunction/ng-clustering 5.39.0 → 5.40.1
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.d.ts +2 -0
- package/dist/__tests__/cluster-view-type.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-view-type.test.js +63 -0
- package/dist/__tests__/cluster-view-type.test.js.map +1 -0
- package/dist/lib/cluster-config-panel.component.d.ts +39 -1
- package/dist/lib/cluster-config-panel.component.d.ts.map +1 -1
- package/dist/lib/cluster-config-panel.component.js +287 -88
- package/dist/lib/cluster-config-panel.component.js.map +1 -1
- package/dist/lib/cluster-scatter.component.d.ts +53 -1
- package/dist/lib/cluster-scatter.component.d.ts.map +1 -1
- package/dist/lib/cluster-scatter.component.js +385 -135
- package/dist/lib/cluster-scatter.component.js.map +1 -1
- package/dist/lib/clustering.module.d.ts +7 -4
- package/dist/lib/clustering.module.d.ts.map +1 -1
- package/dist/lib/clustering.module.js +20 -3
- package/dist/lib/clustering.module.js.map +1 -1
- package/dist/lib/clustering.service.d.ts +63 -3
- package/dist/lib/clustering.service.d.ts.map +1 -1
- package/dist/lib/clustering.service.js +169 -48
- package/dist/lib/clustering.service.js.map +1 -1
- package/dist/lib/clustering.types.d.ts +17 -2
- package/dist/lib/clustering.types.d.ts.map +1 -1
- package/dist/lib/clustering.types.js +3 -0
- package/dist/lib/clustering.types.js.map +1 -1
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +31 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js +185 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -0
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts +143 -0
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-renderer.component.js +508 -0
- package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -0
- package/dist/lib/view-type/cluster-view-type.d.ts +35 -0
- package/dist/lib/view-type/cluster-view-type.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-type.js +51 -0
- package/dist/lib/view-type/cluster-view-type.js.map +1 -0
- package/dist/lib/view-type/cluster-view.types.d.ts +31 -0
- package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view.types.js +29 -0
- package/dist/lib/view-type/cluster-view.types.js.map +1 -0
- package/dist/lib/view-type/entity-document-availability.engine.d.ts +80 -0
- package/dist/lib/view-type/entity-document-availability.engine.d.ts.map +1 -0
- package/dist/lib/view-type/entity-document-availability.engine.js +149 -0
- package/dist/lib/view-type/entity-document-availability.engine.js.map +1 -0
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { EntityInfo } from '@memberjunction/core';
|
|
3
|
+
import { toClusterViewConfig } from './cluster-view.types';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
import * as i1 from "@angular/forms";
|
|
6
|
+
function ClusterViewPropSheetComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
7
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
8
|
+
i0.ɵɵelementStart(0, "label", 1)(1, "span");
|
|
9
|
+
i0.ɵɵtext(2, "Clusters (K)");
|
|
10
|
+
i0.ɵɵelementEnd();
|
|
11
|
+
i0.ɵɵelementStart(3, "input", 11);
|
|
12
|
+
i0.ɵɵlistener("input", function ClusterViewPropSheetComponent_Conditional_9_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.patch({ k: ctx_r1.asInt($event.target.value, ctx_r1.cfg.k) })); });
|
|
13
|
+
i0.ɵɵelementEnd()();
|
|
14
|
+
} if (rf & 2) {
|
|
15
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
16
|
+
i0.ɵɵadvance(3);
|
|
17
|
+
i0.ɵɵproperty("value", ctx_r1.cfg.k);
|
|
18
|
+
} }
|
|
19
|
+
/**
|
|
20
|
+
* ClusterViewPropSheetComponent
|
|
21
|
+
* -----------------------------
|
|
22
|
+
* The configuration prop-sheet for the Cluster view type — honors {@link IViewPropSheet}.
|
|
23
|
+
* Lets the user pick the algorithm, cluster count, 2D/3D layout, legend coloring, record cap,
|
|
24
|
+
* and LLM naming. Emits the updated config (as the opaque host map) on every change so the host
|
|
25
|
+
* can persist it into `UserView.DisplayState.viewTypeConfigs` and re-render the cluster.
|
|
26
|
+
*/
|
|
27
|
+
export class ClusterViewPropSheetComponent {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.entity = null;
|
|
30
|
+
this._config = {};
|
|
31
|
+
this.configChange = new EventEmitter();
|
|
32
|
+
/** The current parsed config (defaults applied) bound by the template. */
|
|
33
|
+
this.cfg = toClusterViewConfig({});
|
|
34
|
+
}
|
|
35
|
+
set config(value) {
|
|
36
|
+
this._config = value ?? {};
|
|
37
|
+
this.cfg = toClusterViewConfig(this._config);
|
|
38
|
+
}
|
|
39
|
+
get config() {
|
|
40
|
+
return this._config;
|
|
41
|
+
}
|
|
42
|
+
/** Apply a partial change, re-emit the full config map to the host. */
|
|
43
|
+
patch(change) {
|
|
44
|
+
this.cfg = { ...this.cfg, ...change };
|
|
45
|
+
this._config = { ...this.cfg };
|
|
46
|
+
this.configChange.emit(this._config);
|
|
47
|
+
}
|
|
48
|
+
asAlgorithm(v) {
|
|
49
|
+
return v === 'dbscan' ? 'dbscan' : 'kmeans';
|
|
50
|
+
}
|
|
51
|
+
asDimensions(v) {
|
|
52
|
+
return String(v) === '3' ? 3 : 2;
|
|
53
|
+
}
|
|
54
|
+
asColorBy(v) {
|
|
55
|
+
return v === 'entity' ? 'entity' : 'cluster';
|
|
56
|
+
}
|
|
57
|
+
asInt(v, fallback) {
|
|
58
|
+
const n = parseInt(v, 10);
|
|
59
|
+
return Number.isNaN(n) ? fallback : n;
|
|
60
|
+
}
|
|
61
|
+
static { this.ɵfac = function ClusterViewPropSheetComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ClusterViewPropSheetComponent)(); }; }
|
|
62
|
+
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
|
+
i0.ɵɵelementStart(0, "div", 0)(1, "label", 1)(2, "span");
|
|
64
|
+
i0.ɵɵtext(3, "Algorithm");
|
|
65
|
+
i0.ɵɵelementEnd();
|
|
66
|
+
i0.ɵɵelementStart(4, "select", 2);
|
|
67
|
+
i0.ɵɵlistener("change", function ClusterViewPropSheetComponent_Template_select_change_4_listener($event) { return ctx.patch({ algorithm: ctx.asAlgorithm($event.target.value) }); });
|
|
68
|
+
i0.ɵɵelementStart(5, "option", 3);
|
|
69
|
+
i0.ɵɵtext(6, "K-Means");
|
|
70
|
+
i0.ɵɵelementEnd();
|
|
71
|
+
i0.ɵɵelementStart(7, "option", 4);
|
|
72
|
+
i0.ɵɵtext(8, "DBSCAN");
|
|
73
|
+
i0.ɵɵelementEnd()()();
|
|
74
|
+
i0.ɵɵconditionalCreate(9, ClusterViewPropSheetComponent_Conditional_9_Template, 4, 1, "label", 1);
|
|
75
|
+
i0.ɵɵelementStart(10, "label", 1)(11, "span");
|
|
76
|
+
i0.ɵɵtext(12, "Layout");
|
|
77
|
+
i0.ɵɵelementEnd();
|
|
78
|
+
i0.ɵɵelementStart(13, "select", 2);
|
|
79
|
+
i0.ɵɵlistener("change", function ClusterViewPropSheetComponent_Template_select_change_13_listener($event) { return ctx.patch({ dimensions: ctx.asDimensions($event.target.value) }); });
|
|
80
|
+
i0.ɵɵelementStart(14, "option", 5);
|
|
81
|
+
i0.ɵɵtext(15, "2D");
|
|
82
|
+
i0.ɵɵelementEnd();
|
|
83
|
+
i0.ɵɵelementStart(16, "option", 5);
|
|
84
|
+
i0.ɵɵtext(17, "3D");
|
|
85
|
+
i0.ɵɵelementEnd()()();
|
|
86
|
+
i0.ɵɵelementStart(18, "label", 1)(19, "span");
|
|
87
|
+
i0.ɵɵtext(20, "Color by");
|
|
88
|
+
i0.ɵɵelementEnd();
|
|
89
|
+
i0.ɵɵelementStart(21, "select", 2);
|
|
90
|
+
i0.ɵɵlistener("change", function ClusterViewPropSheetComponent_Template_select_change_21_listener($event) { return ctx.patch({ colorBy: ctx.asColorBy($event.target.value) }); });
|
|
91
|
+
i0.ɵɵelementStart(22, "option", 6);
|
|
92
|
+
i0.ɵɵtext(23, "Cluster");
|
|
93
|
+
i0.ɵɵelementEnd();
|
|
94
|
+
i0.ɵɵelementStart(24, "option", 7);
|
|
95
|
+
i0.ɵɵtext(25, "Entity");
|
|
96
|
+
i0.ɵɵelementEnd()()();
|
|
97
|
+
i0.ɵɵelementStart(26, "label", 1)(27, "span");
|
|
98
|
+
i0.ɵɵtext(28, "Max records");
|
|
99
|
+
i0.ɵɵelementEnd();
|
|
100
|
+
i0.ɵɵelementStart(29, "input", 8);
|
|
101
|
+
i0.ɵɵlistener("input", function ClusterViewPropSheetComponent_Template_input_input_29_listener($event) { return ctx.patch({ maxRecords: ctx.asInt($event.target.value, ctx.cfg.maxRecords) }); });
|
|
102
|
+
i0.ɵɵelementEnd()();
|
|
103
|
+
i0.ɵɵelementStart(30, "label", 9)(31, "input", 10);
|
|
104
|
+
i0.ɵɵlistener("change", function ClusterViewPropSheetComponent_Template_input_change_31_listener($event) { return ctx.patch({ nameClusters: $event.target.checked }); });
|
|
105
|
+
i0.ɵɵelementEnd();
|
|
106
|
+
i0.ɵɵelementStart(32, "span");
|
|
107
|
+
i0.ɵɵtext(33, "Name clusters with AI");
|
|
108
|
+
i0.ɵɵelementEnd()()();
|
|
109
|
+
} if (rf & 2) {
|
|
110
|
+
i0.ɵɵadvance(4);
|
|
111
|
+
i0.ɵɵproperty("value", ctx.cfg.algorithm);
|
|
112
|
+
i0.ɵɵadvance(5);
|
|
113
|
+
i0.ɵɵconditional(ctx.cfg.algorithm === "kmeans" ? 9 : -1);
|
|
114
|
+
i0.ɵɵadvance(4);
|
|
115
|
+
i0.ɵɵproperty("value", ctx.cfg.dimensions);
|
|
116
|
+
i0.ɵɵadvance();
|
|
117
|
+
i0.ɵɵproperty("value", 2);
|
|
118
|
+
i0.ɵɵadvance(2);
|
|
119
|
+
i0.ɵɵproperty("value", 3);
|
|
120
|
+
i0.ɵɵadvance(5);
|
|
121
|
+
i0.ɵɵproperty("value", ctx.cfg.colorBy);
|
|
122
|
+
i0.ɵɵadvance(8);
|
|
123
|
+
i0.ɵɵproperty("value", ctx.cfg.maxRecords);
|
|
124
|
+
i0.ɵɵadvance(2);
|
|
125
|
+
i0.ɵɵproperty("checked", ctx.cfg.nameClusters);
|
|
126
|
+
} }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption], 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 "], encapsulation: 2 }); }
|
|
127
|
+
}
|
|
128
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusterViewPropSheetComponent, [{
|
|
129
|
+
type: Component,
|
|
130
|
+
args: [{ standalone: false, selector: 'mj-cluster-view-prop-sheet', encapsulation: ViewEncapsulation.None, template: `
|
|
131
|
+
<div class="cluster-prop-sheet">
|
|
132
|
+
<label class="cluster-prop-row">
|
|
133
|
+
<span>Algorithm</span>
|
|
134
|
+
<select class="mj-input" [value]="cfg.algorithm" (change)="patch({ algorithm: asAlgorithm($any($event.target).value) })">
|
|
135
|
+
<option value="kmeans">K-Means</option>
|
|
136
|
+
<option value="dbscan">DBSCAN</option>
|
|
137
|
+
</select>
|
|
138
|
+
</label>
|
|
139
|
+
|
|
140
|
+
@if (cfg.algorithm === 'kmeans') {
|
|
141
|
+
<label class="cluster-prop-row">
|
|
142
|
+
<span>Clusters (K)</span>
|
|
143
|
+
<input class="mj-input" type="number" min="2" max="20" [value]="cfg.k"
|
|
144
|
+
(input)="patch({ k: asInt($any($event.target).value, cfg.k) })" />
|
|
145
|
+
</label>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
<label class="cluster-prop-row">
|
|
149
|
+
<span>Layout</span>
|
|
150
|
+
<select class="mj-input" [value]="cfg.dimensions" (change)="patch({ dimensions: asDimensions($any($event.target).value) })">
|
|
151
|
+
<option [value]="2">2D</option>
|
|
152
|
+
<option [value]="3">3D</option>
|
|
153
|
+
</select>
|
|
154
|
+
</label>
|
|
155
|
+
|
|
156
|
+
<label class="cluster-prop-row">
|
|
157
|
+
<span>Color by</span>
|
|
158
|
+
<select class="mj-input" [value]="cfg.colorBy" (change)="patch({ colorBy: asColorBy($any($event.target).value) })">
|
|
159
|
+
<option value="cluster">Cluster</option>
|
|
160
|
+
<option value="entity">Entity</option>
|
|
161
|
+
</select>
|
|
162
|
+
</label>
|
|
163
|
+
|
|
164
|
+
<label class="cluster-prop-row">
|
|
165
|
+
<span>Max records</span>
|
|
166
|
+
<input class="mj-input" type="number" min="50" max="5000" step="50" [value]="cfg.maxRecords"
|
|
167
|
+
(input)="patch({ maxRecords: asInt($any($event.target).value, cfg.maxRecords) })" />
|
|
168
|
+
</label>
|
|
169
|
+
|
|
170
|
+
<label class="cluster-prop-row cluster-prop-row--inline">
|
|
171
|
+
<input class="mj-checkbox" type="checkbox" [checked]="cfg.nameClusters"
|
|
172
|
+
(change)="patch({ nameClusters: $any($event.target).checked })" />
|
|
173
|
+
<span>Name clusters with AI</span>
|
|
174
|
+
</label>
|
|
175
|
+
</div>
|
|
176
|
+
`, 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 "] }]
|
|
177
|
+
}], null, { entity: [{
|
|
178
|
+
type: Input
|
|
179
|
+
}], config: [{
|
|
180
|
+
type: Input
|
|
181
|
+
}], configChange: [{
|
|
182
|
+
type: Output
|
|
183
|
+
}] }); })();
|
|
184
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ClusterViewPropSheetComponent, { className: "ClusterViewPropSheetComponent", filePath: "src/lib/view-type/cluster-view-prop-sheet.component.ts", lineNumber: 71 }); })();
|
|
185
|
+
//# sourceMappingURL=cluster-view-prop-sheet.component.js.map
|
|
@@ -0,0 +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":"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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 /** 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 asAlgorithm(v: string): 'kmeans' | 'dbscan' {\n return v === 'dbscan' ? 'dbscan' : 'kmeans';\n }\n asDimensions(v: string): 2 | 3 {\n return String(v) === '3' ? 3 : 2;\n }\n asColorBy(v: string): 'cluster' | 'entity' {\n return v === 'entity' ? 'entity' : 'cluster';\n }\n asInt(v: string, fallback: number): number {\n const n = parseInt(v, 10);\n return Number.isNaN(n) ? fallback : n;\n }\n}\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { EventEmitter, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
|
|
2
|
+
import { EntityInfo } from '@memberjunction/core';
|
|
3
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
4
|
+
import { IViewRenderer } from '@memberjunction/ng-entity-viewer';
|
|
5
|
+
import { ClusterPoint, ClusterInfo } from '../clustering.types';
|
|
6
|
+
import { ClusterViewConfig } from './cluster-view.types';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
/**
|
|
9
|
+
* ClusterViewRendererComponent
|
|
10
|
+
* ----------------------------
|
|
11
|
+
* The Cluster **view type** renderer — a thin {@link IViewRenderer} adapter that lets any
|
|
12
|
+
* entity with vectors be visualized as a cluster scatter directly inside `mj-entity-viewer`,
|
|
13
|
+
* reusing the exact same {@link ClusteringService} + `mj-cluster-scatter` the Knowledge Hub
|
|
14
|
+
* "Visualize" cluster surface uses.
|
|
15
|
+
*
|
|
16
|
+
* The host feeds it the standard renderer inputs (entity / records / filterText / config) via
|
|
17
|
+
* `setInput`, and listens for `recordSelected` / `recordOpened`. When the entity or config
|
|
18
|
+
* changes it re-runs the clustering pipeline and rebinds the scatter.
|
|
19
|
+
*
|
|
20
|
+
* **Why this mirrors the Knowledge Hub resource rather than the server `RunClusterAnalysis`
|
|
21
|
+
* path:** the server analysis returns points keyed only by record ID (so labels render as
|
|
22
|
+
* "Unknown"), produces generic "Cluster N" names, and picks an Entity Document
|
|
23
|
+
* non-deterministically when none is supplied (causing flip-flop "no vectors" results). This
|
|
24
|
+
* renderer instead:
|
|
25
|
+
* 1. Resolves the entity's **first active Entity Document explicitly** (deterministic source).
|
|
26
|
+
* 2. Fetches that document's vectors (with their JSON metadata) via {@link GraphQLAIClient.FetchEntityVectors}.
|
|
27
|
+
* 3. Builds real point labels from the entity's `IsNameField` fields ({@link buildLabel}).
|
|
28
|
+
* 4. Clusters in-browser via {@link ClusteringService.RunClustering} (client owns vectors + labels).
|
|
29
|
+
* 5. Requests + applies **LLM cluster names** so clusters read semantically, not "Cluster 1..5".
|
|
30
|
+
*
|
|
31
|
+
* Inputs use the camelCase names mandated by the {@link IViewRenderer} contract (the host
|
|
32
|
+
* binds them by those exact names), rather than MJ's usual PascalCase for public members.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ClusterViewRendererComponent extends BaseAngularComponent implements IViewRenderer<Record<string, unknown>>, OnInit, OnDestroy {
|
|
35
|
+
private cdr;
|
|
36
|
+
private _entity;
|
|
37
|
+
set entity(value: EntityInfo | null);
|
|
38
|
+
get entity(): EntityInfo | null;
|
|
39
|
+
/** The view's records — used to map a clicked point back to its record object. */
|
|
40
|
+
records: Record<string, unknown>[];
|
|
41
|
+
selectedRecordId: string | null;
|
|
42
|
+
filterText: string | null;
|
|
43
|
+
private _config;
|
|
44
|
+
set config(value: Record<string, unknown>);
|
|
45
|
+
get config(): Record<string, unknown>;
|
|
46
|
+
recordSelected: EventEmitter<unknown>;
|
|
47
|
+
recordOpened: EventEmitter<unknown>;
|
|
48
|
+
configChanged: EventEmitter<Record<string, unknown>>;
|
|
49
|
+
/** The current parsed config (defaults applied). */
|
|
50
|
+
activeConfig: ClusterViewConfig;
|
|
51
|
+
points: ClusterPoint[];
|
|
52
|
+
clusters: ClusterInfo[];
|
|
53
|
+
/** Ordered field keys for prioritized display in the scatter tooltip / detail panel. */
|
|
54
|
+
fieldPriority: string[];
|
|
55
|
+
/** Map of field names → human-readable display names for the scatter tooltip / detail panel. */
|
|
56
|
+
fieldDisplayNames: Record<string, string>;
|
|
57
|
+
isLoading: boolean;
|
|
58
|
+
errorMessage: string | null;
|
|
59
|
+
private recluster$;
|
|
60
|
+
private destroy$;
|
|
61
|
+
private clusteringService;
|
|
62
|
+
/**
|
|
63
|
+
* Monotonically increasing token captured at the start of each run. Because runs are async
|
|
64
|
+
* and overlapping (entity/config can change mid-flight), each run compares the token it
|
|
65
|
+
* captured against the latest before applying results — a stale run silently bails so it
|
|
66
|
+
* cannot clobber a newer one.
|
|
67
|
+
*/
|
|
68
|
+
private runToken;
|
|
69
|
+
constructor(cdr: ChangeDetectorRef);
|
|
70
|
+
ngOnInit(): void;
|
|
71
|
+
ngOnDestroy(): void;
|
|
72
|
+
private scheduleRecluster;
|
|
73
|
+
/**
|
|
74
|
+
* Run the clustering pipeline for the current entity + config and rebind the scatter.
|
|
75
|
+
*
|
|
76
|
+
* Mirrors the Knowledge Hub "Visualize" flow: resolve a deterministic Entity Document →
|
|
77
|
+
* fetch its vectors with metadata → build real labels → cluster client-side → request LLM
|
|
78
|
+
* cluster names. Guarded by {@link runToken} so an overlapping/stale run never clobbers a
|
|
79
|
+
* newer one.
|
|
80
|
+
*/
|
|
81
|
+
private runClustering;
|
|
82
|
+
/** True when a newer run has started since `token` was captured — the caller should bail. */
|
|
83
|
+
private isStale;
|
|
84
|
+
/** Apply a successful clustering result to the view (no-op if the run is stale). */
|
|
85
|
+
private applyResult;
|
|
86
|
+
/** Apply an error state to the view (no-op if the run is stale). */
|
|
87
|
+
private applyError;
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the {@link GraphQLDataProvider} backing this component's metadata provider, or
|
|
90
|
+
* `null` when the active provider is not a GraphQL transport (e.g. non-browser).
|
|
91
|
+
*/
|
|
92
|
+
private resolveGraphQLProvider;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the entity's first active Entity Document ID **explicitly** (deterministic),
|
|
95
|
+
* loading the availability engine cache if needed. Returns `null` when the entity has no
|
|
96
|
+
* active document.
|
|
97
|
+
*/
|
|
98
|
+
private resolveEntityDocumentID;
|
|
99
|
+
/**
|
|
100
|
+
* Fetch the entity document's vectors (with metadata) and build {@link ClusterInputVector}s,
|
|
101
|
+
* deriving each point's display label from the entity's name fields.
|
|
102
|
+
*/
|
|
103
|
+
private fetchVectors;
|
|
104
|
+
/** Parse the JSON metadata string stored alongside a vector into a flat string map. */
|
|
105
|
+
private parseVectorMetadata;
|
|
106
|
+
/**
|
|
107
|
+
* Build a human-readable label for a point from the entity's name-field metadata. Combines
|
|
108
|
+
* all `IsNameField` fields in Sequence order (e.g. "Sarah Chen" from FirstName + LastName),
|
|
109
|
+
* then falls back to the single NameField, then to common heuristic keys, then to the record
|
|
110
|
+
* ID — so points show real names instead of "Unknown".
|
|
111
|
+
*/
|
|
112
|
+
private buildLabel;
|
|
113
|
+
/**
|
|
114
|
+
* Compute the prioritized field order + display names for the scatter tooltip / detail panel.
|
|
115
|
+
* Orders: name fields first, then default-in-view fields, then the rest by Sequence; skips
|
|
116
|
+
* internal/virtual/primary-key fields. Also populates {@link fieldDisplayNames}.
|
|
117
|
+
*/
|
|
118
|
+
private computeFieldPriority;
|
|
119
|
+
/** Build the engine {@link ClusterConfig} from the entity + the view-type config. */
|
|
120
|
+
private buildClusterConfig;
|
|
121
|
+
/**
|
|
122
|
+
* After clustering completes, send sample records per cluster to the "Cluster Naming" AI
|
|
123
|
+
* prompt and apply the returned semantic labels to {@link clusters}. Best-effort: failures
|
|
124
|
+
* are logged and leave the provisional "Cluster N" labels in place. No-op if the run became
|
|
125
|
+
* stale while the LLM call was in flight.
|
|
126
|
+
*/
|
|
127
|
+
private requestClusterLabelsFromLLM;
|
|
128
|
+
/** Build a text block describing sample records per cluster for the LLM naming prompt. */
|
|
129
|
+
private buildClusterDataForPrompt;
|
|
130
|
+
/** Apply LLM-generated labels onto the live {@link clusters} array by cluster ID. */
|
|
131
|
+
private applyLabelsToClusters;
|
|
132
|
+
onPointClicked(point: ClusterPoint): void;
|
|
133
|
+
onOpenRecordRequested(point: ClusterPoint): void;
|
|
134
|
+
/**
|
|
135
|
+
* Resolve the host record corresponding to a scatter point. The vector key is the Entity
|
|
136
|
+
* Record Document ID, so match on the point metadata's `RecordID` (the underlying entity
|
|
137
|
+
* record's primary key) first, then fall back to a direct key match.
|
|
138
|
+
*/
|
|
139
|
+
private recordForPoint;
|
|
140
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ClusterViewRendererComponent, never>;
|
|
141
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ClusterViewRendererComponent, "mj-cluster-view-renderer", never, { "entity": { "alias": "entity"; "required": false; }; "records": { "alias": "records"; "required": false; }; "selectedRecordId": { "alias": "selectedRecordId"; "required": false; }; "filterText": { "alias": "filterText"; "required": false; }; "config": { "alias": "config"; "required": false; }; }, { "recordSelected": "recordSelected"; "recordOpened": "recordOpened"; "configChanged": "configChanged"; }, never, never, false, never>;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=cluster-view-renderer.component.d.ts.map
|
|
@@ -0,0 +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;IAyE7H,OAAO,CAAC,GAAG;IAtEvB,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;IAC1D,MAAM,EAAE,YAAY,EAAE,CAAM;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAM;IACpC,wFAAwF;IACjF,aAAa,EAAE,MAAM,EAAE,CAAM;IACpC,gGAAgG;IACzF,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAC/C,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,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIhD;;;;OAIG;IACH,OAAO,CAAC,cAAc;yCAlaX,4BAA4B;2CAA5B,4BAA4B;CAgbxC"}
|