@memberjunction/ng-clustering 5.39.0 → 5.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/__tests__/cluster-view-type.test.d.ts +2 -0
  2. package/dist/__tests__/cluster-view-type.test.d.ts.map +1 -0
  3. package/dist/__tests__/cluster-view-type.test.js +63 -0
  4. package/dist/__tests__/cluster-view-type.test.js.map +1 -0
  5. package/dist/lib/cluster-config-panel.component.d.ts +39 -1
  6. package/dist/lib/cluster-config-panel.component.d.ts.map +1 -1
  7. package/dist/lib/cluster-config-panel.component.js +287 -88
  8. package/dist/lib/cluster-config-panel.component.js.map +1 -1
  9. package/dist/lib/cluster-scatter.component.d.ts +53 -1
  10. package/dist/lib/cluster-scatter.component.d.ts.map +1 -1
  11. package/dist/lib/cluster-scatter.component.js +385 -135
  12. package/dist/lib/cluster-scatter.component.js.map +1 -1
  13. package/dist/lib/clustering.module.d.ts +7 -4
  14. package/dist/lib/clustering.module.d.ts.map +1 -1
  15. package/dist/lib/clustering.module.js +20 -3
  16. package/dist/lib/clustering.module.js.map +1 -1
  17. package/dist/lib/clustering.service.d.ts +63 -3
  18. package/dist/lib/clustering.service.d.ts.map +1 -1
  19. package/dist/lib/clustering.service.js +169 -48
  20. package/dist/lib/clustering.service.js.map +1 -1
  21. package/dist/lib/clustering.types.d.ts +17 -2
  22. package/dist/lib/clustering.types.d.ts.map +1 -1
  23. package/dist/lib/clustering.types.js +3 -0
  24. package/dist/lib/clustering.types.js.map +1 -1
  25. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +31 -0
  26. package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -0
  27. package/dist/lib/view-type/cluster-view-prop-sheet.component.js +185 -0
  28. package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -0
  29. package/dist/lib/view-type/cluster-view-renderer.component.d.ts +143 -0
  30. package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -0
  31. package/dist/lib/view-type/cluster-view-renderer.component.js +508 -0
  32. package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -0
  33. package/dist/lib/view-type/cluster-view-type.d.ts +35 -0
  34. package/dist/lib/view-type/cluster-view-type.d.ts.map +1 -0
  35. package/dist/lib/view-type/cluster-view-type.js +51 -0
  36. package/dist/lib/view-type/cluster-view-type.js.map +1 -0
  37. package/dist/lib/view-type/cluster-view.types.d.ts +31 -0
  38. package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -0
  39. package/dist/lib/view-type/cluster-view.types.js +29 -0
  40. package/dist/lib/view-type/cluster-view.types.js.map +1 -0
  41. package/dist/lib/view-type/entity-document-availability.engine.d.ts +80 -0
  42. package/dist/lib/view-type/entity-document-availability.engine.d.ts.map +1 -0
  43. package/dist/lib/view-type/entity-document-availability.engine.js +149 -0
  44. package/dist/lib/view-type/entity-document-availability.engine.js.map +1 -0
  45. package/dist/public-api.d.ts +5 -0
  46. package/dist/public-api.d.ts.map +1 -1
  47. package/dist/public-api.js +6 -0
  48. package/dist/public-api.js.map +1 -1
  49. package/package.json +11 -3
@@ -66,24 +66,112 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_6_Template(rf, ct
66
66
  i0.ɵɵadvance();
67
67
  i0.ɵɵrepeater(ctx_r2.EntityDocOptions);
68
68
  } }
69
- function ClusterConfigPanelComponent_Conditional_8_Conditional_12_For_5_Template(rf, ctx) { if (rf & 1) {
70
- i0.ɵɵelementStart(0, "option", 9);
69
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_4_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵelementStart(0, "span", 23);
71
+ i0.ɵɵtext(1);
72
+ i0.ɵɵelementEnd();
73
+ } if (rf & 2) {
74
+ const ctx_r2 = i0.ɵɵnextContext(3);
75
+ i0.ɵɵadvance();
76
+ i0.ɵɵtextInterpolate(ctx_r2.SelectedDocIDs.length);
77
+ } }
78
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_For_4_Conditional_4_Template(rf, ctx) { if (rf & 1) {
79
+ i0.ɵɵelementStart(0, "span", 29);
71
80
  i0.ɵɵtext(1);
72
81
  i0.ɵɵelementEnd();
73
82
  } if (rf & 2) {
74
- const alg_r8 = ctx.$implicit;
75
- i0.ɵɵproperty("value", alg_r8.Value);
83
+ const doc_r9 = i0.ɵɵnextContext().$implicit;
76
84
  i0.ɵɵadvance();
77
- i0.ɵɵtextInterpolate(alg_r8.Label);
85
+ i0.ɵɵtextInterpolate(doc_r9.EntityName);
78
86
  } }
79
- function ClusterConfigPanelComponent_Conditional_8_Conditional_12_Template(rf, ctx) { if (rf & 1) {
87
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_For_4_Template(rf, ctx) { if (rf & 1) {
88
+ const _r8 = i0.ɵɵgetCurrentView();
89
+ i0.ɵɵelementStart(0, "label", 26)(1, "input", 27);
90
+ i0.ɵɵlistener("change", function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_For_4_Template_input_change_1_listener() { const doc_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.ToggleDoc(doc_r9.ID)); });
91
+ i0.ɵɵelementEnd();
92
+ i0.ɵɵelementStart(2, "span", 28);
93
+ i0.ɵɵtext(3);
94
+ i0.ɵɵelementEnd();
95
+ i0.ɵɵconditionalCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_For_4_Conditional_4_Template, 2, 1, "span", 29);
96
+ i0.ɵɵelementEnd();
97
+ } if (rf & 2) {
98
+ const doc_r9 = ctx.$implicit;
99
+ const ctx_r2 = i0.ɵɵnextContext(4);
100
+ i0.ɵɵadvance();
101
+ i0.ɵɵproperty("checked", ctx_r2.IsDocSelected(doc_r9.ID));
102
+ i0.ɵɵadvance(2);
103
+ i0.ɵɵtextInterpolate(doc_r9.Name);
104
+ i0.ɵɵadvance();
105
+ i0.ɵɵconditional(doc_r9.EntityName ? 4 : -1);
106
+ } }
107
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_Template(rf, ctx) { if (rf & 1) {
108
+ i0.ɵɵelementStart(0, "div", 24)(1, "div", 25);
109
+ i0.ɵɵtext(2, "Select 2+ documents to cluster them together. All must use the same embedding model.");
110
+ i0.ɵɵelementEnd();
111
+ i0.ɵɵrepeaterCreate(3, ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_For_4_Template, 5, 3, "label", 26, _forTrack0);
112
+ i0.ɵɵelementEnd();
113
+ } if (rf & 2) {
114
+ const ctx_r2 = i0.ɵɵnextContext(3);
115
+ i0.ɵɵadvance(3);
116
+ i0.ɵɵrepeater(ctx_r2.AllEntityDocOptions);
117
+ } }
118
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Template(rf, ctx) { if (rf & 1) {
80
119
  const _r7 = i0.ɵɵgetCurrentView();
120
+ i0.ɵɵelementStart(0, "div", 10)(1, "button", 21);
121
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Conditional_7_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r7); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.ToggleMultiSource()); });
122
+ i0.ɵɵelement(2, "i", 22);
123
+ i0.ɵɵtext(3, " Multiple sources ");
124
+ i0.ɵɵconditionalCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_4_Template, 2, 1, "span", 23);
125
+ i0.ɵɵelementEnd();
126
+ i0.ɵɵconditionalCreate(5, ClusterConfigPanelComponent_Conditional_8_Conditional_7_Conditional_5_Template, 5, 0, "div", 24);
127
+ i0.ɵɵelementEnd();
128
+ } if (rf & 2) {
129
+ const ctx_r2 = i0.ɵɵnextContext(2);
130
+ i0.ɵɵadvance(2);
131
+ i0.ɵɵclassProp("fa-chevron-down", !ctx_r2.ShowMultiSource)("fa-chevron-up", ctx_r2.ShowMultiSource);
132
+ i0.ɵɵadvance(2);
133
+ i0.ɵɵconditional(ctx_r2.IsMultiEntity ? 4 : -1);
134
+ i0.ɵɵadvance();
135
+ i0.ɵɵconditional(ctx_r2.ShowMultiSource ? 5 : -1);
136
+ } }
137
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_9_Template(rf, ctx) { if (rf & 1) {
138
+ const _r10 = i0.ɵɵgetCurrentView();
139
+ i0.ɵɵelementStart(0, "div", 7)(1, "label");
140
+ i0.ɵɵtext(2, "Projection");
141
+ i0.ɵɵelementEnd();
142
+ i0.ɵɵelementStart(3, "div", 12)(4, "button", 13);
143
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Conditional_9_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r10); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.SetDimensions(2)); });
144
+ i0.ɵɵtext(5, "2D");
145
+ i0.ɵɵelementEnd();
146
+ i0.ɵɵelementStart(6, "button", 13);
147
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Conditional_9_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r10); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.SetDimensions(3)); });
148
+ i0.ɵɵtext(7, "3D");
149
+ i0.ɵɵelementEnd()()();
150
+ } if (rf & 2) {
151
+ const ctx_r2 = i0.ɵɵnextContext(2);
152
+ i0.ɵɵadvance(4);
153
+ i0.ɵɵclassProp("active", (ctx_r2.Config.Dimensions ?? 2) === 2);
154
+ i0.ɵɵadvance(2);
155
+ i0.ɵɵclassProp("active", ctx_r2.Config.Dimensions === 3);
156
+ } }
157
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_23_For_5_Template(rf, ctx) { if (rf & 1) {
158
+ i0.ɵɵelementStart(0, "option", 9);
159
+ i0.ɵɵtext(1);
160
+ i0.ɵɵelementEnd();
161
+ } if (rf & 2) {
162
+ const alg_r12 = ctx.$implicit;
163
+ i0.ɵɵproperty("value", alg_r12.Value);
164
+ i0.ɵɵadvance();
165
+ i0.ɵɵtextInterpolate(alg_r12.Label);
166
+ } }
167
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_23_Template(rf, ctx) { if (rf & 1) {
168
+ const _r11 = i0.ɵɵgetCurrentView();
81
169
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
82
170
  i0.ɵɵtext(2, "Algorithm");
83
171
  i0.ɵɵelementEnd();
84
172
  i0.ɵɵelementStart(3, "select", 8);
85
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_12_Template_select_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnAlgorithmChanged($event)); });
86
- i0.ɵɵrepeaterCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_12_For_5_Template, 2, 2, "option", 9, i0.ɵɵcomponentInstance().TrackAlgorithmBy, true);
173
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_23_Template_select_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r11); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnAlgorithmChanged($event)); });
174
+ i0.ɵɵrepeaterCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_23_For_5_Template, 2, 2, "option", 9, i0.ɵɵcomponentInstance().TrackAlgorithmBy, true);
87
175
  i0.ɵɵelementEnd()();
88
176
  } if (rf & 2) {
89
177
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -92,13 +180,13 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_12_Template(rf, c
92
180
  i0.ɵɵadvance();
93
181
  i0.ɵɵrepeater(ctx_r2.AlgorithmOptions);
94
182
  } }
95
- function ClusterConfigPanelComponent_Conditional_8_Conditional_13_Template(rf, ctx) { if (rf & 1) {
96
- const _r9 = i0.ɵɵgetCurrentView();
183
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_24_Template(rf, ctx) { if (rf & 1) {
184
+ const _r13 = i0.ɵɵgetCurrentView();
97
185
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
98
186
  i0.ɵɵtext(2);
99
187
  i0.ɵɵelementEnd();
100
- i0.ɵɵelementStart(3, "input", 16);
101
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_13_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.K = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
188
+ i0.ɵɵelementStart(3, "input", 30);
189
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_24_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r13); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.K = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
102
190
  i0.ɵɵelementEnd()();
103
191
  } if (rf & 2) {
104
192
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -107,13 +195,13 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_13_Template(rf, c
107
195
  i0.ɵɵadvance();
108
196
  i0.ɵɵproperty("ngModel", ctx_r2.Config.K)("min", 2)("max", 20)("step", 1);
109
197
  } }
110
- function ClusterConfigPanelComponent_Conditional_8_Conditional_14_Template(rf, ctx) { if (rf & 1) {
111
- const _r10 = i0.ɵɵgetCurrentView();
198
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template(rf, ctx) { if (rf & 1) {
199
+ const _r14 = i0.ɵɵgetCurrentView();
112
200
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
113
201
  i0.ɵɵtext(2);
114
202
  i0.ɵɵelementEnd();
115
- i0.ɵɵelementStart(3, "input", 16);
116
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_14_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r10); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.Epsilon = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
203
+ i0.ɵɵelementStart(3, "input", 30);
204
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r14); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.Epsilon = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
117
205
  i0.ɵɵelementEnd()();
118
206
  } if (rf & 2) {
119
207
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -122,13 +210,13 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_14_Template(rf, c
122
210
  i0.ɵɵadvance();
123
211
  i0.ɵɵproperty("ngModel", ctx_r2.Config.Epsilon)("min", 0.05)("max", 0.95)("step", 0.05);
124
212
  } }
125
- function ClusterConfigPanelComponent_Conditional_8_Conditional_15_Template(rf, ctx) { if (rf & 1) {
126
- const _r11 = i0.ɵɵgetCurrentView();
213
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template(rf, ctx) { if (rf & 1) {
214
+ const _r15 = i0.ɵɵgetCurrentView();
127
215
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
128
216
  i0.ɵɵtext(2);
129
217
  i0.ɵɵelementEnd();
130
- i0.ɵɵelementStart(3, "input", 16);
131
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_15_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r11); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.MinPoints = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
218
+ i0.ɵɵelementStart(3, "input", 30);
219
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template_input_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r15); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.Config.MinPoints = $event; return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
132
220
  i0.ɵɵelementEnd()();
133
221
  } if (rf & 2) {
134
222
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -137,24 +225,24 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_15_Template(rf, c
137
225
  i0.ɵɵadvance();
138
226
  i0.ɵɵproperty("ngModel", ctx_r2.Config.MinPoints)("min", 2)("max", 20)("step", 1);
139
227
  } }
140
- function ClusterConfigPanelComponent_Conditional_8_Conditional_17_For_5_Template(rf, ctx) { if (rf & 1) {
228
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_28_For_5_Template(rf, ctx) { if (rf & 1) {
141
229
  i0.ɵɵelementStart(0, "option", 9);
142
230
  i0.ɵɵtext(1);
143
231
  i0.ɵɵelementEnd();
144
232
  } if (rf & 2) {
145
- const metric_r13 = ctx.$implicit;
146
- i0.ɵɵproperty("value", metric_r13.Value);
233
+ const metric_r17 = ctx.$implicit;
234
+ i0.ɵɵproperty("value", metric_r17.Value);
147
235
  i0.ɵɵadvance();
148
- i0.ɵɵtextInterpolate(metric_r13.Label);
236
+ i0.ɵɵtextInterpolate(metric_r17.Label);
149
237
  } }
150
- function ClusterConfigPanelComponent_Conditional_8_Conditional_17_Template(rf, ctx) { if (rf & 1) {
151
- const _r12 = i0.ɵɵgetCurrentView();
238
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_28_Template(rf, ctx) { if (rf & 1) {
239
+ const _r16 = i0.ɵɵgetCurrentView();
152
240
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
153
241
  i0.ɵɵtext(2, "Distance Metric");
154
242
  i0.ɵɵelementEnd();
155
243
  i0.ɵɵelementStart(3, "select", 8);
156
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_17_Template_select_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r12); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnMetricChanged($event)); });
157
- i0.ɵɵrepeaterCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_17_For_5_Template, 2, 2, "option", 9, i0.ɵɵcomponentInstance().TrackMetricBy, true);
244
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Conditional_28_Template_select_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnMetricChanged($event)); });
245
+ i0.ɵɵrepeaterCreate(4, ClusterConfigPanelComponent_Conditional_8_Conditional_28_For_5_Template, 2, 2, "option", 9, i0.ɵɵcomponentInstance().TrackMetricBy, true);
158
246
  i0.ɵɵelementEnd()();
159
247
  } if (rf & 2) {
160
248
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -163,19 +251,27 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_17_Template(rf, c
163
251
  i0.ɵɵadvance();
164
252
  i0.ɵɵrepeater(ctx_r2.MetricOptions);
165
253
  } }
166
- function ClusterConfigPanelComponent_Conditional_8_Conditional_23_Template(rf, ctx) { if (rf & 1) {
167
- i0.ɵɵelement(0, "i", 17);
254
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_35_Template(rf, ctx) { if (rf & 1) {
255
+ i0.ɵɵelement(0, "i", 31);
168
256
  i0.ɵɵtext(1, " Running... ");
169
257
  } }
170
- function ClusterConfigPanelComponent_Conditional_8_Conditional_24_Template(rf, ctx) { if (rf & 1) {
171
- i0.ɵɵelement(0, "i", 18);
258
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_36_Template(rf, ctx) { if (rf & 1) {
259
+ i0.ɵɵelement(0, "i", 32);
172
260
  i0.ɵɵtext(1, " Run Clustering ");
173
261
  } }
174
- function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Conditional_21_Template(rf, ctx) { if (rf & 1) {
175
- i0.ɵɵelementStart(0, "div", 19)(1, "span", 20);
262
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_37_Template(rf, ctx) { if (rf & 1) {
263
+ const _r18 = i0.ɵɵgetCurrentView();
264
+ i0.ɵɵelementStart(0, "button", 33);
265
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Conditional_37_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnSave()); });
266
+ i0.ɵɵelement(1, "i", 34);
267
+ i0.ɵɵtext(2, " Save ");
268
+ i0.ɵɵelementEnd();
269
+ } }
270
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_38_Conditional_21_Template(rf, ctx) { if (rf & 1) {
271
+ i0.ɵɵelementStart(0, "div", 35)(1, "span", 36);
176
272
  i0.ɵɵtext(2, "Outliers");
177
273
  i0.ɵɵelementEnd();
178
- i0.ɵɵelementStart(3, "span", 21);
274
+ i0.ɵɵelementStart(3, "span", 37);
179
275
  i0.ɵɵtext(4);
180
276
  i0.ɵɵelementEnd()();
181
277
  } if (rf & 2) {
@@ -183,32 +279,32 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Conditional_21
183
279
  i0.ɵɵadvance(4);
184
280
  i0.ɵɵtextInterpolate(ctx_r2.Metrics.OutlierCount);
185
281
  } }
186
- function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template(rf, ctx) { if (rf & 1) {
187
- i0.ɵɵelementStart(0, "div", 14)(1, "div", 19)(2, "span", 20);
282
+ function ClusterConfigPanelComponent_Conditional_8_Conditional_38_Template(rf, ctx) { if (rf & 1) {
283
+ i0.ɵɵelementStart(0, "div", 20)(1, "div", 35)(2, "span", 36);
188
284
  i0.ɵɵtext(3, "Silhouette");
189
285
  i0.ɵɵelementEnd();
190
- i0.ɵɵelementStart(4, "span", 21);
286
+ i0.ɵɵelementStart(4, "span", 37);
191
287
  i0.ɵɵtext(5);
192
288
  i0.ɵɵelementEnd()();
193
- i0.ɵɵelementStart(6, "div", 19)(7, "span", 20);
289
+ i0.ɵɵelementStart(6, "div", 35)(7, "span", 36);
194
290
  i0.ɵɵtext(8, "Clusters");
195
291
  i0.ɵɵelementEnd();
196
- i0.ɵɵelementStart(9, "span", 21);
292
+ i0.ɵɵelementStart(9, "span", 37);
197
293
  i0.ɵɵtext(10);
198
294
  i0.ɵɵelementEnd()();
199
- i0.ɵɵelementStart(11, "div", 19)(12, "span", 20);
295
+ i0.ɵɵelementStart(11, "div", 35)(12, "span", 36);
200
296
  i0.ɵɵtext(13, "Records");
201
297
  i0.ɵɵelementEnd();
202
- i0.ɵɵelementStart(14, "span", 21);
298
+ i0.ɵɵelementStart(14, "span", 37);
203
299
  i0.ɵɵtext(15);
204
300
  i0.ɵɵelementEnd()();
205
- i0.ɵɵelementStart(16, "div", 19)(17, "span", 20);
301
+ i0.ɵɵelementStart(16, "div", 35)(17, "span", 36);
206
302
  i0.ɵɵtext(18, "Time");
207
303
  i0.ɵɵelementEnd();
208
- i0.ɵɵelementStart(19, "span", 21);
304
+ i0.ɵɵelementStart(19, "span", 37);
209
305
  i0.ɵɵtext(20);
210
306
  i0.ɵɵelementEnd()();
211
- i0.ɵɵconditionalCreate(21, ClusterConfigPanelComponent_Conditional_8_Conditional_25_Conditional_21_Template, 5, 1, "div", 19);
307
+ i0.ɵɵconditionalCreate(21, ClusterConfigPanelComponent_Conditional_8_Conditional_38_Conditional_21_Template, 5, 1, "div", 35);
212
308
  i0.ɵɵelementEnd();
213
309
  } if (rf & 2) {
214
310
  const ctx_r2 = i0.ɵɵnextContext(2);
@@ -225,14 +321,6 @@ function ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template(rf, c
225
321
  i0.ɵɵadvance();
226
322
  i0.ɵɵconditional(ctx_r2.Metrics.OutlierCount > 0 ? 21 : -1);
227
323
  } }
228
- function ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template(rf, ctx) { if (rf & 1) {
229
- const _r14 = i0.ɵɵgetCurrentView();
230
- i0.ɵɵelementStart(0, "div", 22);
231
- i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r14); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnSave()); });
232
- i0.ɵɵelement(1, "i", 23);
233
- i0.ɵɵtext(2, " Save this visualization ");
234
- i0.ɵɵelementEnd();
235
- } }
236
324
  function ClusterConfigPanelComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
237
325
  const _r2 = i0.ɵɵgetCurrentView();
238
326
  i0.ɵɵelementStart(0, "div", 7)(1, "label");
@@ -243,33 +331,48 @@ function ClusterConfigPanelComponent_Conditional_8_Template(rf, ctx) { if (rf &
243
331
  i0.ɵɵrepeaterCreate(4, ClusterConfigPanelComponent_Conditional_8_For_5_Template, 2, 2, "option", 9, i0.ɵɵcomponentInstance().TrackEntityBy, true);
244
332
  i0.ɵɵelementEnd()();
245
333
  i0.ɵɵconditionalCreate(6, ClusterConfigPanelComponent_Conditional_8_Conditional_6_Template, 6, 1, "div", 7);
246
- i0.ɵɵelementStart(7, "div", 7)(8, "label");
247
- i0.ɵɵtext(9, "Filter (optional)");
334
+ i0.ɵɵconditionalCreate(7, ClusterConfigPanelComponent_Conditional_8_Conditional_7_Template, 6, 6, "div", 10);
335
+ i0.ɵɵelementStart(8, "div", 11);
336
+ i0.ɵɵconditionalCreate(9, ClusterConfigPanelComponent_Conditional_8_Conditional_9_Template, 8, 4, "div", 7);
337
+ i0.ɵɵelementStart(10, "div", 7)(11, "label");
338
+ i0.ɵɵtext(12, "Color by");
248
339
  i0.ɵɵelementEnd();
249
- i0.ɵɵelementStart(10, "input", 10);
250
- i0.ɵɵtwoWayListener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_10_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.Config.Filter, $event) || (ctx_r2.Config.Filter = $event); return i0.ɵɵresetView($event); });
251
- i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_10_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
252
- i0.ɵɵelementEnd()();
253
- i0.ɵɵelementStart(11, "div", 11);
254
- i0.ɵɵconditionalCreate(12, ClusterConfigPanelComponent_Conditional_8_Conditional_12_Template, 6, 1, "div", 7);
255
- i0.ɵɵconditionalCreate(13, ClusterConfigPanelComponent_Conditional_8_Conditional_13_Template, 4, 5, "div", 7)(14, ClusterConfigPanelComponent_Conditional_8_Conditional_14_Template, 4, 5, "div", 7);
340
+ i0.ɵɵelementStart(13, "div", 12)(14, "button", 13);
341
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.SetColorBy("cluster")); });
342
+ i0.ɵɵtext(15, "Cluster");
256
343
  i0.ɵɵelementEnd();
257
- i0.ɵɵconditionalCreate(15, ClusterConfigPanelComponent_Conditional_8_Conditional_15_Template, 4, 5, "div", 7);
258
- i0.ɵɵelementStart(16, "div", 11);
259
- i0.ɵɵconditionalCreate(17, ClusterConfigPanelComponent_Conditional_8_Conditional_17_Template, 6, 1, "div", 7);
344
+ i0.ɵɵelementStart(16, "button", 13);
345
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Template_button_click_16_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.SetColorBy("entity")); });
346
+ i0.ɵɵtext(17, "Entity");
347
+ i0.ɵɵelementEnd()()()();
260
348
  i0.ɵɵelementStart(18, "div", 7)(19, "label");
261
- i0.ɵɵtext(20, "Max Records");
349
+ i0.ɵɵtext(20, "Filter (optional)");
262
350
  i0.ɵɵelementEnd();
263
- i0.ɵɵelementStart(21, "input", 12);
264
- i0.ɵɵtwoWayListener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_21_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.Config.MaxRecords, $event) || (ctx_r2.Config.MaxRecords = $event); return i0.ɵɵresetView($event); });
351
+ i0.ɵɵelementStart(21, "input", 14);
352
+ i0.ɵɵtwoWayListener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_21_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.Config.Filter, $event) || (ctx_r2.Config.Filter = $event); return i0.ɵɵresetView($event); });
265
353
  i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_21_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
354
+ i0.ɵɵelementEnd()();
355
+ i0.ɵɵelementStart(22, "div", 15);
356
+ i0.ɵɵconditionalCreate(23, ClusterConfigPanelComponent_Conditional_8_Conditional_23_Template, 6, 1, "div", 7);
357
+ i0.ɵɵconditionalCreate(24, ClusterConfigPanelComponent_Conditional_8_Conditional_24_Template, 4, 5, "div", 7)(25, ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template, 4, 5, "div", 7);
358
+ i0.ɵɵelementEnd();
359
+ i0.ɵɵconditionalCreate(26, ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template, 4, 5, "div", 7);
360
+ i0.ɵɵelementStart(27, "div", 15);
361
+ i0.ɵɵconditionalCreate(28, ClusterConfigPanelComponent_Conditional_8_Conditional_28_Template, 6, 1, "div", 7);
362
+ i0.ɵɵelementStart(29, "div", 7)(30, "label");
363
+ i0.ɵɵtext(31, "Max Records");
364
+ i0.ɵɵelementEnd();
365
+ i0.ɵɵelementStart(32, "input", 16);
366
+ i0.ɵɵtwoWayListener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_32_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.Config.MaxRecords, $event) || (ctx_r2.Config.MaxRecords = $event); return i0.ɵɵresetView($event); });
367
+ i0.ɵɵlistener("ngModelChange", function ClusterConfigPanelComponent_Conditional_8_Template_input_ngModelChange_32_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnFieldChanged()); });
266
368
  i0.ɵɵelementEnd()()();
267
- i0.ɵɵelementStart(22, "button", 13);
268
- i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Template_button_click_22_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnRun()); });
269
- i0.ɵɵconditionalCreate(23, ClusterConfigPanelComponent_Conditional_8_Conditional_23_Template, 2, 0)(24, ClusterConfigPanelComponent_Conditional_8_Conditional_24_Template, 2, 0);
369
+ i0.ɵɵelementStart(33, "div", 17)(34, "button", 18);
370
+ i0.ɵɵlistener("click", function ClusterConfigPanelComponent_Conditional_8_Template_button_click_34_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnRun()); });
371
+ i0.ɵɵconditionalCreate(35, ClusterConfigPanelComponent_Conditional_8_Conditional_35_Template, 2, 0)(36, ClusterConfigPanelComponent_Conditional_8_Conditional_36_Template, 2, 0);
270
372
  i0.ɵɵelementEnd();
271
- i0.ɵɵconditionalCreate(25, ClusterConfigPanelComponent_Conditional_8_Conditional_25_Template, 22, 7, "div", 14);
272
- i0.ɵɵconditionalCreate(26, ClusterConfigPanelComponent_Conditional_8_Conditional_26_Template, 3, 0, "div", 15);
373
+ i0.ɵɵconditionalCreate(37, ClusterConfigPanelComponent_Conditional_8_Conditional_37_Template, 3, 0, "button", 19);
374
+ i0.ɵɵelementEnd();
375
+ i0.ɵɵconditionalCreate(38, ClusterConfigPanelComponent_Conditional_8_Conditional_38_Template, 22, 7, "div", 20);
273
376
  } if (rf & 2) {
274
377
  const ctx_r2 = i0.ɵɵnextContext();
275
378
  i0.ɵɵadvance(3);
@@ -278,27 +381,35 @@ function ClusterConfigPanelComponent_Conditional_8_Template(rf, ctx) { if (rf &
278
381
  i0.ɵɵrepeater(ctx_r2.FilteredEntityOptions);
279
382
  i0.ɵɵadvance(2);
280
383
  i0.ɵɵconditional(ctx_r2.EntityDocOptions.length > 1 ? 6 : -1);
281
- i0.ɵɵadvance(4);
384
+ i0.ɵɵadvance();
385
+ i0.ɵɵconditional(ctx_r2.HasMultiSourceOptions ? 7 : -1);
386
+ i0.ɵɵadvance(2);
387
+ i0.ɵɵconditional(ctx_r2.ShowDimensionToggle ? 9 : -1);
388
+ i0.ɵɵadvance(5);
389
+ i0.ɵɵclassProp("active", (ctx_r2.Config.ColorBy ?? "cluster") === "cluster");
390
+ i0.ɵɵadvance(2);
391
+ i0.ɵɵclassProp("active", ctx_r2.Config.ColorBy === "entity");
392
+ i0.ɵɵadvance(5);
282
393
  i0.ɵɵtwoWayProperty("ngModel", ctx_r2.Config.Filter);
283
394
  i0.ɵɵadvance(2);
284
- i0.ɵɵconditional(ctx_r2.ShowAlgorithmPicker ? 12 : -1);
395
+ i0.ɵɵconditional(ctx_r2.ShowAlgorithmPicker ? 23 : -1);
285
396
  i0.ɵɵadvance();
286
- i0.ɵɵconditional(ctx_r2.IsKMeans ? 13 : 14);
397
+ i0.ɵɵconditional(ctx_r2.IsKMeans ? 24 : 25);
287
398
  i0.ɵɵadvance(2);
288
- i0.ɵɵconditional(!ctx_r2.IsKMeans ? 15 : -1);
399
+ i0.ɵɵconditional(!ctx_r2.IsKMeans ? 26 : -1);
289
400
  i0.ɵɵadvance(2);
290
- i0.ɵɵconditional(ctx_r2.ShowMetricPicker ? 17 : -1);
401
+ i0.ɵɵconditional(ctx_r2.ShowMetricPicker ? 28 : -1);
291
402
  i0.ɵɵadvance(4);
292
403
  i0.ɵɵtwoWayProperty("ngModel", ctx_r2.Config.MaxRecords);
293
404
  i0.ɵɵproperty("min", 10)("max", 5000);
405
+ i0.ɵɵadvance(2);
406
+ i0.ɵɵproperty("disabled", !ctx_r2.CanRun);
294
407
  i0.ɵɵadvance();
295
- i0.ɵɵproperty("disabled", ctx_r2.IsRunning || !ctx_r2.Config.EntityName);
296
- i0.ɵɵadvance();
297
- i0.ɵɵconditional(ctx_r2.IsRunning ? 23 : 24);
408
+ i0.ɵɵconditional(ctx_r2.IsRunning ? 35 : 36);
298
409
  i0.ɵɵadvance(2);
299
- i0.ɵɵconditional(ctx_r2.Metrics && ctx_r2.Metrics.RecordCount > 0 ? 25 : -1);
410
+ i0.ɵɵconditional(ctx_r2.CanSave ? 37 : -1);
300
411
  i0.ɵɵadvance();
301
- i0.ɵɵconditional(ctx_r2.ShowSaveButton ? 26 : -1);
412
+ i0.ɵɵconditional(ctx_r2.Metrics && ctx_r2.Metrics.RecordCount > 0 ? 38 : -1);
302
413
  } }
303
414
  export class ClusterConfigPanelComponent {
304
415
  constructor() {
@@ -331,6 +442,16 @@ export class ClusterConfigPanelComponent {
331
442
  this._entityOptions = [];
332
443
  /** Entity Document options for the selected entity. Only shown when 2+ docs exist. */
333
444
  this.EntityDocOptions = [];
445
+ /**
446
+ * All Entity Documents across entities (each with `EntityName`), used to power
447
+ * the optional multi-entity source selector. When two or more are selected the
448
+ * run becomes a multi-entity analysis (their vectors are merged server-side).
449
+ */
450
+ this.AllEntityDocOptions = [];
451
+ /** Whether to show the 2D/3D projection toggle. @default true */
452
+ this.ShowDimensionToggle = true;
453
+ /** Whether to show the multi-entity source selector. @default true */
454
+ this.ShowMultiEntity = true;
334
455
  // ================================================================
335
456
  // Customization Inputs
336
457
  // ================================================================
@@ -438,6 +559,12 @@ export class ClusterConfigPanelComponent {
438
559
  * Useful for showing/hiding algorithm-specific UI in the parent.
439
560
  */
440
561
  this.AlgorithmChanged = new EventEmitter();
562
+ /**
563
+ * Fires when the 2D/3D projection toggle changes. Hosts should re-run (or
564
+ * re-project) so the change is reflected immediately — a 3D projection needs
565
+ * a Z coordinate that only a fresh run produces.
566
+ */
567
+ this.DimensionsChanged = new EventEmitter();
441
568
  // ================================================================
442
569
  // Internal State
443
570
  // ================================================================
@@ -447,6 +574,8 @@ export class ClusterConfigPanelComponent {
447
574
  this.FilteredEntityOptions = [];
448
575
  /** Whether the panel body is currently visible. */
449
576
  this.IsExpanded = true;
577
+ /** Whether the multi-entity source selector is expanded. */
578
+ this.ShowMultiSource = false;
450
579
  /** Algorithm options for the dropdown. */
451
580
  this.AlgorithmOptions = [
452
581
  { Value: 'kmeans', Label: 'K-Means' },
@@ -544,12 +673,74 @@ export class ClusterConfigPanelComponent {
544
673
  OnFieldChanged() {
545
674
  this.emitConfigChanged();
546
675
  }
676
+ // ----- Multi-entity / dimensions / color-by -----
677
+ /** The currently selected entity-document IDs for multi-entity clustering. */
678
+ get SelectedDocIDs() {
679
+ return this.Config.EntityDocumentIDs ?? [];
680
+ }
681
+ /** True when two or more source documents are selected (multi-entity mode). */
682
+ get IsMultiEntity() {
683
+ return this.SelectedDocIDs.length >= 2;
684
+ }
685
+ /** Whether there are enough documents to offer multi-entity selection. */
686
+ get HasMultiSourceOptions() {
687
+ return this.ShowMultiEntity && this.AllEntityDocOptions.length > 1;
688
+ }
689
+ /** Whether a given document is currently selected. */
690
+ IsDocSelected(id) {
691
+ return this.SelectedDocIDs.includes(id);
692
+ }
693
+ /** Toggle a document in/out of the multi-entity selection. */
694
+ ToggleDoc(id) {
695
+ const set = new Set(this.Config.EntityDocumentIDs ?? []);
696
+ if (set.has(id)) {
697
+ set.delete(id);
698
+ }
699
+ else {
700
+ set.add(id);
701
+ }
702
+ this.Config.EntityDocumentIDs = [...set];
703
+ // Once a multi-entity selection exists, color-by-entity is the useful default.
704
+ if (this.IsMultiEntity && this.Config.ColorBy !== 'entity') {
705
+ this.Config.ColorBy = 'entity';
706
+ }
707
+ this.emitConfigChanged();
708
+ this.cdr.detectChanges();
709
+ }
710
+ /** Toggle the multi-source expander. */
711
+ ToggleMultiSource() {
712
+ this.ShowMultiSource = !this.ShowMultiSource;
713
+ this.cdr.detectChanges();
714
+ }
715
+ /** Set the projection dimensionality (2 or 3). */
716
+ SetDimensions(d) {
717
+ if (this.Config.Dimensions === d)
718
+ return;
719
+ this.Config.Dimensions = d;
720
+ this.emitConfigChanged();
721
+ this.DimensionsChanged.emit(d);
722
+ this.cdr.detectChanges();
723
+ }
724
+ /** Set the legend/color mode. */
725
+ SetColorBy(mode) {
726
+ this.Config.ColorBy = mode;
727
+ this.emitConfigChanged();
728
+ this.cdr.detectChanges();
729
+ }
730
+ /** Whether the Run button can be clicked. */
731
+ get CanRun() {
732
+ return !this.IsRunning && (!!this.Config.EntityName || this.SelectedDocIDs.length > 0);
733
+ }
734
+ /** Whether the Save button should be offered (there are results to save). */
735
+ get CanSave() {
736
+ return this.ShowSaveButton && !!this.Metrics && this.Metrics.RecordCount > 0;
737
+ }
547
738
  /**
548
739
  * Handle Run button click. Fires `BeforeRunClustering`, and if not
549
740
  * canceled, emits `RunClustering`.
550
741
  */
551
742
  OnRun() {
552
- if (!this.Config.EntityName || this.IsRunning)
743
+ if (!this.CanRun)
553
744
  return;
554
745
  const snapshot = { ...this.Config };
555
746
  const cancelable = { Data: snapshot, Cancel: false };
@@ -668,7 +859,7 @@ export class ClusterConfigPanelComponent {
668
859
  } if (rf & 2) {
669
860
  let _t;
670
861
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.panelRef = _t.first);
671
- } }, inputs: { IsRunning: "IsRunning", Metrics: "Metrics", EntityOptions: "EntityOptions", EntityDocOptions: "EntityDocOptions", ShowSaveButton: "ShowSaveButton", ShowAlgorithmPicker: "ShowAlgorithmPicker", ShowMetricPicker: "ShowMetricPicker", DefaultAlgorithm: "DefaultAlgorithm", AvailableEntities: "AvailableEntities", Collapsed: "Collapsed" }, outputs: { RunClustering: "RunClustering", SaveVisualization: "SaveVisualization", BeforeRunClustering: "BeforeRunClustering", BeforeSave: "BeforeSave", ConfigChanged: "ConfigChanged", AlgorithmChanged: "AlgorithmChanged" }, standalone: false, decls: 9, vars: 9, consts: [["panelElement", ""], [1, "config-panel"], [1, "panel-header", 3, "mousedown"], [1, "fa-solid", "fa-sliders"], [1, "header-actions"], ["title", "Toggle panel", 1, "fa-solid", 3, "click"], [1, "fa-solid", "fa-grip-dots-vertical", "drag-handle"], [1, "form-group"], [3, "ngModelChange", "ngModel"], [3, "value"], ["type", "text", "placeholder", "e.g. Status = 'Active'", 3, "ngModelChange", "ngModel"], [1, "form-row"], ["type", "number", 3, "ngModelChange", "ngModel", "min", "max"], [1, "run-btn", 3, "click", "disabled"], [1, "metrics-section"], [1, "save-link"], ["type", "range", 3, "ngModelChange", "ngModel", "min", "max", "step"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"], [1, "metric-item"], [1, "metric-label"], [1, "metric-value"], [1, "save-link", 3, "click"], [1, "fa-solid", "fa-bookmark"]], template: function ClusterConfigPanelComponent_Template(rf, ctx) { if (rf & 1) {
862
+ } }, inputs: { IsRunning: "IsRunning", Metrics: "Metrics", EntityOptions: "EntityOptions", EntityDocOptions: "EntityDocOptions", AllEntityDocOptions: "AllEntityDocOptions", ShowDimensionToggle: "ShowDimensionToggle", ShowMultiEntity: "ShowMultiEntity", ShowSaveButton: "ShowSaveButton", ShowAlgorithmPicker: "ShowAlgorithmPicker", ShowMetricPicker: "ShowMetricPicker", DefaultAlgorithm: "DefaultAlgorithm", AvailableEntities: "AvailableEntities", Collapsed: "Collapsed" }, outputs: { RunClustering: "RunClustering", SaveVisualization: "SaveVisualization", BeforeRunClustering: "BeforeRunClustering", BeforeSave: "BeforeSave", ConfigChanged: "ConfigChanged", AlgorithmChanged: "AlgorithmChanged", DimensionsChanged: "DimensionsChanged" }, standalone: false, decls: 9, vars: 9, consts: [["panelElement", ""], [1, "config-panel"], [1, "panel-header", 3, "mousedown"], [1, "fa-solid", "fa-sliders"], [1, "header-actions"], ["title", "Toggle panel", 1, "fa-solid", 3, "click"], [1, "fa-solid", "fa-grip-dots-vertical", "drag-handle"], [1, "form-group"], [3, "ngModelChange", "ngModel"], [3, "value"], [1, "form-group", "multi-source"], [1, "form-row", "toggle-row"], ["role", "group", 1, "seg-toggle"], ["type", "button", 1, "seg-btn", 3, "click"], ["type", "text", "placeholder", "e.g. Status = 'Active'", 3, "ngModelChange", "ngModel"], [1, "form-row"], ["type", "number", 3, "ngModelChange", "ngModel", "min", "max"], [1, "action-row"], [1, "run-btn", 3, "click", "disabled"], ["title", "Save this visualization", 1, "save-btn"], [1, "metrics-section"], ["type", "button", 1, "multi-source-toggle", 3, "click"], [1, "fa-solid"], [1, "multi-source-badge"], [1, "multi-source-list"], [1, "multi-source-hint"], [1, "multi-source-item"], ["type", "checkbox", 3, "change", "checked"], [1, "multi-source-name"], [1, "multi-source-entity"], ["type", "range", 3, "ngModelChange", "ngModel", "min", "max", "step"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"], ["title", "Save this visualization", 1, "save-btn", 3, "click"], [1, "fa-solid", "fa-bookmark"], [1, "metric-item"], [1, "metric-label"], [1, "metric-value"]], template: function ClusterConfigPanelComponent_Template(rf, ctx) { if (rf & 1) {
672
863
  const _r1 = i0.ɵɵgetCurrentView();
673
864
  i0.ɵɵelementStart(0, "div", 1, 0)(2, "h3", 2);
674
865
  i0.ɵɵlistener("mousedown", function ClusterConfigPanelComponent_Template_h3_mousedown_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnHeaderMouseDown($event)); });
@@ -679,7 +870,7 @@ export class ClusterConfigPanelComponent {
679
870
  i0.ɵɵelementEnd();
680
871
  i0.ɵɵelement(7, "i", 6);
681
872
  i0.ɵɵelementEnd()();
682
- i0.ɵɵconditionalCreate(8, ClusterConfigPanelComponent_Conditional_8_Template, 27, 14);
873
+ i0.ɵɵconditionalCreate(8, ClusterConfigPanelComponent_Conditional_8_Template, 39, 20);
683
874
  i0.ɵɵelementEnd();
684
875
  } if (rf & 2) {
685
876
  i0.ɵɵstyleProp("top", ctx.PanelTop, "px")("left", ctx.PanelLeft, "px");
@@ -687,11 +878,11 @@ export class ClusterConfigPanelComponent {
687
878
  i0.ɵɵclassProp("fa-chevron-up", ctx.IsExpanded)("fa-chevron-down", !ctx.IsExpanded);
688
879
  i0.ɵɵadvance(2);
689
880
  i0.ɵɵconditional(ctx.IsExpanded ? 8 : -1);
690
- } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.RangeValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel], styles: [".config-panel[_ngcontent-%COMP%] {\n position: absolute;\n width: 300px;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 95%, transparent);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid var(--mj-border-default);\n border-radius: 14px;\n padding: 20px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n z-index: 10;\n}\n\n.panel-header[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n font-weight: 600;\n margin: 0 0 16px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n cursor: move;\n user-select: none;\n}\n\n.panel-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child {\n color: var(--mj-brand-primary);\n}\n\n.header-actions[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.header-actions[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.header-actions[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n.drag-handle[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n\n\n.form-group[_ngcontent-%COMP%] {\n margin-bottom: 14px;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n font-size: 0.7rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 5px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.form-group[_ngcontent-%COMP%] select[_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] input[type=\"text\"][_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] input[type=\"number\"][_ngcontent-%COMP%] {\n width: 100%;\n padding: 8px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n color: var(--mj-text-primary);\n font-size: 0.82rem;\n outline: none;\n transition: border 0.15s;\n}\n\n.form-group[_ngcontent-%COMP%] select[_ngcontent-%COMP%]:focus, \n.form-group[_ngcontent-%COMP%] input[type=\"text\"][_ngcontent-%COMP%]:focus, \n.form-group[_ngcontent-%COMP%] input[type=\"number\"][_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n}\n\n.form-group[_ngcontent-%COMP%] input[type=\"range\"][_ngcontent-%COMP%] {\n width: 100%;\n accent-color: var(--mj-brand-primary);\n}\n\n.form-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n}\n\n.form-row[_ngcontent-%COMP%] .form-group[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n.run-btn[_ngcontent-%COMP%] {\n width: 100%;\n padding: 10px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 8px;\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: background 0.15s;\n margin-top: 4px;\n}\n\n.run-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n\n\n.metrics-section[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-top: 14px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.metric-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.metric-label[_ngcontent-%COMP%] {\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n}\n\n.metric-value[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.metric-value.good[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}\n\n\n\n.save-link[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n margin-top: 10px;\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.save-link[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary);\n}"] }); }
881
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.NumberValueAccessor, i1.RangeValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.MinValidator, i1.MaxValidator, i1.NgModel], styles: [".config-panel[_ngcontent-%COMP%] {\n position: absolute;\n width: 300px;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 95%, transparent);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid var(--mj-border-default);\n border-radius: 14px;\n padding: 20px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n z-index: 10;\n}\n\n.panel-header[_ngcontent-%COMP%] {\n font-size: 0.8rem;\n font-weight: 600;\n margin: 0 0 16px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n cursor: move;\n user-select: none;\n}\n\n.panel-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:first-child {\n color: var(--mj-brand-primary);\n}\n\n.header-actions[_ngcontent-%COMP%] {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.header-actions[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.header-actions[_ngcontent-%COMP%] i[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n.drag-handle[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n\n\n.form-group[_ngcontent-%COMP%] {\n margin-bottom: 14px;\n}\n\n.form-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n font-size: 0.7rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 5px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.form-group[_ngcontent-%COMP%] select[_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] input[type=\"text\"][_ngcontent-%COMP%], \n.form-group[_ngcontent-%COMP%] input[type=\"number\"][_ngcontent-%COMP%] {\n width: 100%;\n padding: 8px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n color: var(--mj-text-primary);\n font-size: 0.82rem;\n outline: none;\n transition: border 0.15s;\n}\n\n.form-group[_ngcontent-%COMP%] select[_ngcontent-%COMP%]:focus, \n.form-group[_ngcontent-%COMP%] input[type=\"text\"][_ngcontent-%COMP%]:focus, \n.form-group[_ngcontent-%COMP%] input[type=\"number\"][_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n}\n\n.form-group[_ngcontent-%COMP%] input[type=\"range\"][_ngcontent-%COMP%] {\n width: 100%;\n accent-color: var(--mj-brand-primary);\n}\n\n.form-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n}\n\n.form-row[_ngcontent-%COMP%] .form-group[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n.action-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: 4px;\n}\n\n.run-btn[_ngcontent-%COMP%] {\n flex: 1;\n padding: 10px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 8px;\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: background 0.15s;\n}\n\n.run-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.save-btn[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n padding: 10px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.save-btn[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n\n\n.toggle-row[_ngcontent-%COMP%] .form-group[_ngcontent-%COMP%] { min-width: 0; }\n\n.seg-toggle[_ngcontent-%COMP%] {\n display: inline-flex;\n gap: 2px;\n padding: 3px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n width: 100%;\n}\n\n.seg-btn[_ngcontent-%COMP%] {\n flex: 1;\n padding: 5px 8px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.75rem;\n font-weight: 600;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n}\n\n.seg-btn[_ngcontent-%COMP%]:hover { color: var(--mj-text-primary); }\n\n.seg-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n\n\n.multi-source-toggle[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n}\n\n.multi-source-toggle[_ngcontent-%COMP%]:hover { color: var(--mj-text-primary); }\n\n.multi-source-badge[_ngcontent-%COMP%] {\n margin-left: auto;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-radius: 10px;\n padding: 1px 8px;\n font-size: 0.7rem;\n}\n\n.multi-source-list[_ngcontent-%COMP%] {\n margin-top: 6px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 8px;\n max-height: 180px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n}\n\n.multi-source-hint[_ngcontent-%COMP%] {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n margin-bottom: 6px;\n line-height: 1.3;\n}\n\n.multi-source-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 4px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n}\n\n.multi-source-item[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-hover); border-radius: 6px; }\n\n.multi-source-entity[_ngcontent-%COMP%] {\n margin-left: auto;\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n}\n\n\n\n.metrics-section[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-top: 14px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.metric-item[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.metric-label[_ngcontent-%COMP%] {\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n}\n\n.metric-value[_ngcontent-%COMP%] {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.metric-value.good[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n}"] }); }
691
882
  }
692
883
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusterConfigPanelComponent, [{
693
884
  type: Component,
694
- args: [{ standalone: false, selector: 'mj-cluster-config-panel', template: "<div #panelElement\n class=\"config-panel\"\n [style.top.px]=\"PanelTop\"\n [style.left.px]=\"PanelLeft\">\n\n <!-- Draggable header -->\n <h3 class=\"panel-header\"\n (mousedown)=\"OnHeaderMouseDown($event)\">\n <i class=\"fa-solid fa-sliders\"></i>\n Configuration\n <span class=\"header-actions\">\n <i class=\"fa-solid\"\n [class.fa-chevron-up]=\"IsExpanded\"\n [class.fa-chevron-down]=\"!IsExpanded\"\n (click)=\"ToggleCollapse(); $event.stopPropagation()\"\n title=\"Toggle panel\"></i>\n <i class=\"fa-solid fa-grip-dots-vertical drag-handle\"></i>\n </span>\n </h3>\n\n @if (IsExpanded) {\n <!-- Entity picker -->\n <div class=\"form-group\">\n <label>Entity</label>\n <select [ngModel]=\"Config.EntityName\"\n (ngModelChange)=\"OnEntityChanged($event)\">\n @for (entity of FilteredEntityOptions; track TrackEntityBy($index, entity)) {\n <option [value]=\"entity.Name\">{{ entity.Name }}</option>\n }\n </select>\n </div>\n\n <!-- Entity Document selector (only shown when 2+ docs for selected entity) -->\n @if (EntityDocOptions.length > 1) {\n <div class=\"form-group\">\n <label>Document Template</label>\n <select [ngModel]=\"Config.EntityDocumentID\"\n (ngModelChange)=\"Config.EntityDocumentID = $event; OnFieldChanged()\">\n @for (doc of EntityDocOptions; track doc.ID) {\n <option [value]=\"doc.ID\">{{ doc.Name }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Filter -->\n <div class=\"form-group\">\n <label>Filter (optional)</label>\n <input type=\"text\"\n [(ngModel)]=\"Config.Filter\"\n (ngModelChange)=\"OnFieldChanged()\"\n placeholder=\"e.g. Status = 'Active'\">\n </div>\n\n <!-- Algorithm + K/Epsilon row -->\n <div class=\"form-row\">\n @if (ShowAlgorithmPicker) {\n <div class=\"form-group\">\n <label>Algorithm</label>\n <select [ngModel]=\"Config.Algorithm\"\n (ngModelChange)=\"OnAlgorithmChanged($event)\">\n @for (alg of AlgorithmOptions; track TrackAlgorithmBy($index, alg)) {\n <option [value]=\"alg.Value\">{{ alg.Label }}</option>\n }\n </select>\n </div>\n }\n\n @if (IsKMeans) {\n <div class=\"form-group\">\n <label>Clusters (K): {{ Config.K }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.K\"\n (ngModelChange)=\"Config.K = $event; OnFieldChanged()\"\n [min]=\"2\"\n [max]=\"20\"\n [step]=\"1\">\n </div>\n } @else {\n <div class=\"form-group\">\n <label>Epsilon: {{ Config.Epsilon }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.Epsilon\"\n (ngModelChange)=\"Config.Epsilon = $event; OnFieldChanged()\"\n [min]=\"0.05\"\n [max]=\"0.95\"\n [step]=\"0.05\">\n </div>\n }\n </div>\n\n <!-- DBSCAN MinPoints row (only visible for DBSCAN) -->\n @if (!IsKMeans) {\n <div class=\"form-group\">\n <label>Min Points: {{ Config.MinPoints }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.MinPoints\"\n (ngModelChange)=\"Config.MinPoints = $event; OnFieldChanged()\"\n [min]=\"2\"\n [max]=\"20\"\n [step]=\"1\">\n </div>\n }\n\n <!-- Distance metric + Max records row -->\n <div class=\"form-row\">\n @if (ShowMetricPicker) {\n <div class=\"form-group\">\n <label>Distance Metric</label>\n <select [ngModel]=\"Config.DistanceMetric\"\n (ngModelChange)=\"OnMetricChanged($event)\">\n @for (metric of MetricOptions; track TrackMetricBy($index, metric)) {\n <option [value]=\"metric.Value\">{{ metric.Label }}</option>\n }\n </select>\n </div>\n }\n <div class=\"form-group\">\n <label>Max Records</label>\n <input type=\"number\"\n [(ngModel)]=\"Config.MaxRecords\"\n (ngModelChange)=\"OnFieldChanged()\"\n [min]=\"10\"\n [max]=\"5000\">\n </div>\n </div>\n\n <!-- Run button -->\n <button class=\"run-btn\"\n [disabled]=\"IsRunning || !Config.EntityName\"\n (click)=\"OnRun()\">\n @if (IsRunning) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running...\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run Clustering\n }\n </button>\n\n <!-- Metrics display -->\n @if (Metrics && Metrics.RecordCount > 0) {\n <div class=\"metrics-section\">\n <div class=\"metric-item\">\n <span class=\"metric-label\">Silhouette</span>\n <span class=\"metric-value\"\n [class.good]=\"IsSilhouetteGood(Metrics.SilhouetteScore)\">\n {{ FormatSilhouette(Metrics.SilhouetteScore) }}\n </span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Clusters</span>\n <span class=\"metric-value\">{{ Metrics.ClusterCount }}</span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Records</span>\n <span class=\"metric-value\">{{ Metrics.RecordCount }}</span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Time</span>\n <span class=\"metric-value\">{{ FormatTime(Metrics.ComputationTimeMs) }}</span>\n </div>\n @if (Metrics.OutlierCount > 0) {\n <div class=\"metric-item\">\n <span class=\"metric-label\">Outliers</span>\n <span class=\"metric-value\">{{ Metrics.OutlierCount }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Save link -->\n @if (ShowSaveButton) {\n <div class=\"save-link\" (click)=\"OnSave()\">\n <i class=\"fa-solid fa-bookmark\"></i> Save this visualization\n </div>\n }\n }\n</div>\n", styles: [".config-panel {\n position: absolute;\n width: 300px;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 95%, transparent);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid var(--mj-border-default);\n border-radius: 14px;\n padding: 20px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n z-index: 10;\n}\n\n.panel-header {\n font-size: 0.8rem;\n font-weight: 600;\n margin: 0 0 16px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n cursor: move;\n user-select: none;\n}\n\n.panel-header i:first-child {\n color: var(--mj-brand-primary);\n}\n\n.header-actions {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.header-actions i {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.header-actions i:hover {\n color: var(--mj-text-primary);\n}\n\n.drag-handle {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n/* Form elements */\n.form-group {\n margin-bottom: 14px;\n}\n\n.form-group label {\n display: block;\n font-size: 0.7rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 5px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.form-group select,\n.form-group input[type=\"text\"],\n.form-group input[type=\"number\"] {\n width: 100%;\n padding: 8px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n color: var(--mj-text-primary);\n font-size: 0.82rem;\n outline: none;\n transition: border 0.15s;\n}\n\n.form-group select:focus,\n.form-group input[type=\"text\"]:focus,\n.form-group input[type=\"number\"]:focus {\n border-color: var(--mj-brand-primary);\n}\n\n.form-group input[type=\"range\"] {\n width: 100%;\n accent-color: var(--mj-brand-primary);\n}\n\n.form-row {\n display: flex;\n gap: 10px;\n}\n\n.form-row .form-group {\n flex: 1;\n}\n\n/* Run button */\n.run-btn {\n width: 100%;\n padding: 10px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 8px;\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: background 0.15s;\n margin-top: 4px;\n}\n\n.run-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* Metrics */\n.metrics-section {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-top: 14px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.metric-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.metric-label {\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n}\n\n.metric-value {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.metric-value.good {\n color: var(--mj-status-success);\n}\n\n/* Save link */\n.save-link {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n margin-top: 10px;\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.save-link:hover {\n color: var(--mj-brand-primary);\n}\n\n"] }]
885
+ args: [{ standalone: false, selector: 'mj-cluster-config-panel', template: "<div #panelElement\n class=\"config-panel\"\n [style.top.px]=\"PanelTop\"\n [style.left.px]=\"PanelLeft\">\n\n <!-- Draggable header -->\n <h3 class=\"panel-header\"\n (mousedown)=\"OnHeaderMouseDown($event)\">\n <i class=\"fa-solid fa-sliders\"></i>\n Configuration\n <span class=\"header-actions\">\n <i class=\"fa-solid\"\n [class.fa-chevron-up]=\"IsExpanded\"\n [class.fa-chevron-down]=\"!IsExpanded\"\n (click)=\"ToggleCollapse(); $event.stopPropagation()\"\n title=\"Toggle panel\"></i>\n <i class=\"fa-solid fa-grip-dots-vertical drag-handle\"></i>\n </span>\n </h3>\n\n @if (IsExpanded) {\n <!-- Entity picker -->\n <div class=\"form-group\">\n <label>Entity</label>\n <select [ngModel]=\"Config.EntityName\"\n (ngModelChange)=\"OnEntityChanged($event)\">\n @for (entity of FilteredEntityOptions; track TrackEntityBy($index, entity)) {\n <option [value]=\"entity.Name\">{{ entity.Name }}</option>\n }\n </select>\n </div>\n\n <!-- Entity Document selector (only shown when 2+ docs for selected entity) -->\n @if (EntityDocOptions.length > 1) {\n <div class=\"form-group\">\n <label>Document Template</label>\n <select [ngModel]=\"Config.EntityDocumentID\"\n (ngModelChange)=\"Config.EntityDocumentID = $event; OnFieldChanged()\">\n @for (doc of EntityDocOptions; track doc.ID) {\n <option [value]=\"doc.ID\">{{ doc.Name }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Multi-entity source selector -->\n @if (HasMultiSourceOptions) {\n <div class=\"form-group multi-source\">\n <button type=\"button\" class=\"multi-source-toggle\" (click)=\"ToggleMultiSource()\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!ShowMultiSource\" [class.fa-chevron-up]=\"ShowMultiSource\"></i>\n Multiple sources\n @if (IsMultiEntity) {\n <span class=\"multi-source-badge\">{{ SelectedDocIDs.length }}</span>\n }\n </button>\n @if (ShowMultiSource) {\n <div class=\"multi-source-list\">\n <div class=\"multi-source-hint\">Select 2+ documents to cluster them together. All must use the same embedding model.</div>\n @for (doc of AllEntityDocOptions; track doc.ID) {\n <label class=\"multi-source-item\">\n <input type=\"checkbox\" [checked]=\"IsDocSelected(doc.ID)\" (change)=\"ToggleDoc(doc.ID)\">\n <span class=\"multi-source-name\">{{ doc.Name }}</span>\n @if (doc.EntityName) { <span class=\"multi-source-entity\">{{ doc.EntityName }}</span> }\n </label>\n }\n </div>\n }\n </div>\n }\n\n <!-- Projection (2D / 3D) + Color-by toggles -->\n <div class=\"form-row toggle-row\">\n @if (ShowDimensionToggle) {\n <div class=\"form-group\">\n <label>Projection</label>\n <div class=\"seg-toggle\" role=\"group\">\n <button type=\"button\" class=\"seg-btn\" [class.active]=\"(Config.Dimensions ?? 2) === 2\" (click)=\"SetDimensions(2)\">2D</button>\n <button type=\"button\" class=\"seg-btn\" [class.active]=\"Config.Dimensions === 3\" (click)=\"SetDimensions(3)\">3D</button>\n </div>\n </div>\n }\n <div class=\"form-group\">\n <label>Color by</label>\n <div class=\"seg-toggle\" role=\"group\">\n <button type=\"button\" class=\"seg-btn\" [class.active]=\"(Config.ColorBy ?? 'cluster') === 'cluster'\" (click)=\"SetColorBy('cluster')\">Cluster</button>\n <button type=\"button\" class=\"seg-btn\" [class.active]=\"Config.ColorBy === 'entity'\" (click)=\"SetColorBy('entity')\">Entity</button>\n </div>\n </div>\n </div>\n\n <!-- Filter -->\n <div class=\"form-group\">\n <label>Filter (optional)</label>\n <input type=\"text\"\n [(ngModel)]=\"Config.Filter\"\n (ngModelChange)=\"OnFieldChanged()\"\n placeholder=\"e.g. Status = 'Active'\">\n </div>\n\n <!-- Algorithm + K/Epsilon row -->\n <div class=\"form-row\">\n @if (ShowAlgorithmPicker) {\n <div class=\"form-group\">\n <label>Algorithm</label>\n <select [ngModel]=\"Config.Algorithm\"\n (ngModelChange)=\"OnAlgorithmChanged($event)\">\n @for (alg of AlgorithmOptions; track TrackAlgorithmBy($index, alg)) {\n <option [value]=\"alg.Value\">{{ alg.Label }}</option>\n }\n </select>\n </div>\n }\n\n @if (IsKMeans) {\n <div class=\"form-group\">\n <label>Clusters (K): {{ Config.K }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.K\"\n (ngModelChange)=\"Config.K = $event; OnFieldChanged()\"\n [min]=\"2\"\n [max]=\"20\"\n [step]=\"1\">\n </div>\n } @else {\n <div class=\"form-group\">\n <label>Epsilon: {{ Config.Epsilon }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.Epsilon\"\n (ngModelChange)=\"Config.Epsilon = $event; OnFieldChanged()\"\n [min]=\"0.05\"\n [max]=\"0.95\"\n [step]=\"0.05\">\n </div>\n }\n </div>\n\n <!-- DBSCAN MinPoints row (only visible for DBSCAN) -->\n @if (!IsKMeans) {\n <div class=\"form-group\">\n <label>Min Points: {{ Config.MinPoints }}</label>\n <input type=\"range\"\n [ngModel]=\"Config.MinPoints\"\n (ngModelChange)=\"Config.MinPoints = $event; OnFieldChanged()\"\n [min]=\"2\"\n [max]=\"20\"\n [step]=\"1\">\n </div>\n }\n\n <!-- Distance metric + Max records row -->\n <div class=\"form-row\">\n @if (ShowMetricPicker) {\n <div class=\"form-group\">\n <label>Distance Metric</label>\n <select [ngModel]=\"Config.DistanceMetric\"\n (ngModelChange)=\"OnMetricChanged($event)\">\n @for (metric of MetricOptions; track TrackMetricBy($index, metric)) {\n <option [value]=\"metric.Value\">{{ metric.Label }}</option>\n }\n </select>\n </div>\n }\n <div class=\"form-group\">\n <label>Max Records</label>\n <input type=\"number\"\n [(ngModel)]=\"Config.MaxRecords\"\n (ngModelChange)=\"OnFieldChanged()\"\n [min]=\"10\"\n [max]=\"5000\">\n </div>\n </div>\n\n <!-- Run + Save action row -->\n <div class=\"action-row\">\n <button class=\"run-btn\"\n [disabled]=\"!CanRun\"\n (click)=\"OnRun()\">\n @if (IsRunning) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running...\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run Clustering\n }\n </button>\n @if (CanSave) {\n <button class=\"save-btn\" (click)=\"OnSave()\" title=\"Save this visualization\">\n <i class=\"fa-solid fa-bookmark\"></i> Save\n </button>\n }\n </div>\n\n <!-- Metrics display -->\n @if (Metrics && Metrics.RecordCount > 0) {\n <div class=\"metrics-section\">\n <div class=\"metric-item\">\n <span class=\"metric-label\">Silhouette</span>\n <span class=\"metric-value\"\n [class.good]=\"IsSilhouetteGood(Metrics.SilhouetteScore)\">\n {{ FormatSilhouette(Metrics.SilhouetteScore) }}\n </span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Clusters</span>\n <span class=\"metric-value\">{{ Metrics.ClusterCount }}</span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Records</span>\n <span class=\"metric-value\">{{ Metrics.RecordCount }}</span>\n </div>\n <div class=\"metric-item\">\n <span class=\"metric-label\">Time</span>\n <span class=\"metric-value\">{{ FormatTime(Metrics.ComputationTimeMs) }}</span>\n </div>\n @if (Metrics.OutlierCount > 0) {\n <div class=\"metric-item\">\n <span class=\"metric-label\">Outliers</span>\n <span class=\"metric-value\">{{ Metrics.OutlierCount }}</span>\n </div>\n }\n </div>\n }\n\n }\n</div>\n", styles: [".config-panel {\n position: absolute;\n width: 300px;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 95%, transparent);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border: 1px solid var(--mj-border-default);\n border-radius: 14px;\n padding: 20px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n z-index: 10;\n}\n\n.panel-header {\n font-size: 0.8rem;\n font-weight: 600;\n margin: 0 0 16px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-primary);\n cursor: move;\n user-select: none;\n}\n\n.panel-header i:first-child {\n color: var(--mj-brand-primary);\n}\n\n.header-actions {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.header-actions i {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n cursor: pointer;\n transition: color 0.15s;\n}\n\n.header-actions i:hover {\n color: var(--mj-text-primary);\n}\n\n.drag-handle {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n/* Form elements */\n.form-group {\n margin-bottom: 14px;\n}\n\n.form-group label {\n display: block;\n font-size: 0.7rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n margin-bottom: 5px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.form-group select,\n.form-group input[type=\"text\"],\n.form-group input[type=\"number\"] {\n width: 100%;\n padding: 8px 12px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 7px;\n color: var(--mj-text-primary);\n font-size: 0.82rem;\n outline: none;\n transition: border 0.15s;\n}\n\n.form-group select:focus,\n.form-group input[type=\"text\"]:focus,\n.form-group input[type=\"number\"]:focus {\n border-color: var(--mj-brand-primary);\n}\n\n.form-group input[type=\"range\"] {\n width: 100%;\n accent-color: var(--mj-brand-primary);\n}\n\n.form-row {\n display: flex;\n gap: 10px;\n}\n\n.form-row .form-group {\n flex: 1;\n}\n\n/* Run button */\n.action-row {\n display: flex;\n gap: 8px;\n margin-top: 4px;\n}\n\n.run-btn {\n flex: 1;\n padding: 10px;\n background: var(--mj-brand-primary);\n border: none;\n border-radius: 8px;\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: background 0.15s;\n}\n\n.run-btn:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n}\n\n.run-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.save-btn {\n flex: 0 0 auto;\n padding: 10px 14px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.15s;\n}\n\n.save-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n}\n\n/* Segmented toggles (Projection 2D/3D, Color-by) */\n.toggle-row .form-group { min-width: 0; }\n\n.seg-toggle {\n display: inline-flex;\n gap: 2px;\n padding: 3px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n width: 100%;\n}\n\n.seg-btn {\n flex: 1;\n padding: 5px 8px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.75rem;\n font-weight: 600;\n border-radius: 6px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n}\n\n.seg-btn:hover { color: var(--mj-text-primary); }\n\n.seg-btn.active {\n background: var(--mj-bg-surface);\n color: var(--mj-brand-primary);\n box-shadow: 0 1px 2px color-mix(in srgb, var(--mj-text-primary) 12%, transparent);\n}\n\n/* Multi-entity source selector */\n.multi-source-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n font-weight: 600;\n cursor: pointer;\n}\n\n.multi-source-toggle:hover { color: var(--mj-text-primary); }\n\n.multi-source-badge {\n margin-left: auto;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-radius: 10px;\n padding: 1px 8px;\n font-size: 0.7rem;\n}\n\n.multi-source-list {\n margin-top: 6px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 8px;\n max-height: 180px;\n overflow-y: auto;\n background: var(--mj-bg-surface);\n}\n\n.multi-source-hint {\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n margin-bottom: 6px;\n line-height: 1.3;\n}\n\n.multi-source-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 4px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n}\n\n.multi-source-item:hover { background: var(--mj-bg-surface-hover); border-radius: 6px; }\n\n.multi-source-entity {\n margin-left: auto;\n font-size: 0.68rem;\n color: var(--mj-text-muted);\n}\n\n/* Metrics */\n.metrics-section {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-top: 14px;\n padding-top: 14px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.metric-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.metric-label {\n font-size: 0.65rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--mj-text-muted);\n}\n\n.metric-value {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.metric-value.good {\n color: var(--mj-status-success);\n}\n\n\n"] }]
695
886
  }], null, { IsRunning: [{
696
887
  type: Input
697
888
  }], Metrics: [{
@@ -700,6 +891,12 @@ export class ClusterConfigPanelComponent {
700
891
  type: Input
701
892
  }], EntityDocOptions: [{
702
893
  type: Input
894
+ }], AllEntityDocOptions: [{
895
+ type: Input
896
+ }], ShowDimensionToggle: [{
897
+ type: Input
898
+ }], ShowMultiEntity: [{
899
+ type: Input
703
900
  }], ShowSaveButton: [{
704
901
  type: Input
705
902
  }], ShowAlgorithmPicker: [{
@@ -724,6 +921,8 @@ export class ClusterConfigPanelComponent {
724
921
  type: Output
725
922
  }], AlgorithmChanged: [{
726
923
  type: Output
924
+ }], DimensionsChanged: [{
925
+ type: Output
727
926
  }], panelRef: [{
728
927
  type: ViewChild,
729
928
  args: ['panelElement', { static: false }]