@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
|
@@ -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
|
|
70
|
-
i0.ɵɵelementStart(0, "
|
|
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
|
|
75
|
-
i0.ɵɵproperty("value", alg_r8.Value);
|
|
83
|
+
const doc_r9 = i0.ɵɵnextContext().$implicit;
|
|
76
84
|
i0.ɵɵadvance();
|
|
77
|
-
i0.ɵɵtextInterpolate(
|
|
85
|
+
i0.ɵɵtextInterpolate(doc_r9.EntityName);
|
|
78
86
|
} }
|
|
79
|
-
function
|
|
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
|
|
86
|
-
i0.ɵɵrepeaterCreate(4,
|
|
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
|
|
96
|
-
const
|
|
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",
|
|
101
|
-
i0.ɵɵlistener("ngModelChange", function
|
|
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
|
|
111
|
-
const
|
|
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",
|
|
116
|
-
i0.ɵɵlistener("ngModelChange", function
|
|
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
|
|
126
|
-
const
|
|
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",
|
|
131
|
-
i0.ɵɵlistener("ngModelChange", function
|
|
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
|
|
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
|
|
146
|
-
i0.ɵɵproperty("value",
|
|
233
|
+
const metric_r17 = ctx.$implicit;
|
|
234
|
+
i0.ɵɵproperty("value", metric_r17.Value);
|
|
147
235
|
i0.ɵɵadvance();
|
|
148
|
-
i0.ɵɵtextInterpolate(
|
|
236
|
+
i0.ɵɵtextInterpolate(metric_r17.Label);
|
|
149
237
|
} }
|
|
150
|
-
function
|
|
151
|
-
const
|
|
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
|
|
157
|
-
i0.ɵɵrepeaterCreate(4,
|
|
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
|
|
167
|
-
i0.ɵɵelement(0, "i",
|
|
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
|
|
171
|
-
i0.ɵɵelement(0, "i",
|
|
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
|
|
175
|
-
i0.ɵɵ
|
|
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",
|
|
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
|
|
187
|
-
i0.ɵɵelementStart(0, "div",
|
|
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",
|
|
286
|
+
i0.ɵɵelementStart(4, "span", 37);
|
|
191
287
|
i0.ɵɵtext(5);
|
|
192
288
|
i0.ɵɵelementEnd()();
|
|
193
|
-
i0.ɵɵelementStart(6, "div",
|
|
289
|
+
i0.ɵɵelementStart(6, "div", 35)(7, "span", 36);
|
|
194
290
|
i0.ɵɵtext(8, "Clusters");
|
|
195
291
|
i0.ɵɵelementEnd();
|
|
196
|
-
i0.ɵɵelementStart(9, "span",
|
|
292
|
+
i0.ɵɵelementStart(9, "span", 37);
|
|
197
293
|
i0.ɵɵtext(10);
|
|
198
294
|
i0.ɵɵelementEnd()();
|
|
199
|
-
i0.ɵɵelementStart(11, "div",
|
|
295
|
+
i0.ɵɵelementStart(11, "div", 35)(12, "span", 36);
|
|
200
296
|
i0.ɵɵtext(13, "Records");
|
|
201
297
|
i0.ɵɵelementEnd();
|
|
202
|
-
i0.ɵɵelementStart(14, "span",
|
|
298
|
+
i0.ɵɵelementStart(14, "span", 37);
|
|
203
299
|
i0.ɵɵtext(15);
|
|
204
300
|
i0.ɵɵelementEnd()();
|
|
205
|
-
i0.ɵɵelementStart(16, "div",
|
|
301
|
+
i0.ɵɵelementStart(16, "div", 35)(17, "span", 36);
|
|
206
302
|
i0.ɵɵtext(18, "Time");
|
|
207
303
|
i0.ɵɵelementEnd();
|
|
208
|
-
i0.ɵɵelementStart(19, "span",
|
|
304
|
+
i0.ɵɵelementStart(19, "span", 37);
|
|
209
305
|
i0.ɵɵtext(20);
|
|
210
306
|
i0.ɵɵelementEnd()();
|
|
211
|
-
i0.ɵɵconditionalCreate(21,
|
|
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.ɵɵ
|
|
247
|
-
i0.ɵɵ
|
|
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(
|
|
250
|
-
i0.ɵɵ
|
|
251
|
-
i0.ɵɵ
|
|
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.ɵɵ
|
|
258
|
-
i0.ɵɵ
|
|
259
|
-
i0.ɵɵ
|
|
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, "
|
|
349
|
+
i0.ɵɵtext(20, "Filter (optional)");
|
|
262
350
|
i0.ɵɵelementEnd();
|
|
263
|
-
i0.ɵɵelementStart(21, "input",
|
|
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.
|
|
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(
|
|
268
|
-
i0.ɵɵlistener("click", function
|
|
269
|
-
i0.ɵɵconditionalCreate(
|
|
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(
|
|
272
|
-
i0.ɵɵ
|
|
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(
|
|
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 ?
|
|
395
|
+
i0.ɵɵconditional(ctx_r2.ShowAlgorithmPicker ? 23 : -1);
|
|
285
396
|
i0.ɵɵadvance();
|
|
286
|
-
i0.ɵɵconditional(ctx_r2.IsKMeans ?
|
|
397
|
+
i0.ɵɵconditional(ctx_r2.IsKMeans ? 24 : 25);
|
|
287
398
|
i0.ɵɵadvance(2);
|
|
288
|
-
i0.ɵɵconditional(!ctx_r2.IsKMeans ?
|
|
399
|
+
i0.ɵɵconditional(!ctx_r2.IsKMeans ? 26 : -1);
|
|
289
400
|
i0.ɵɵadvance(2);
|
|
290
|
-
i0.ɵɵconditional(ctx_r2.ShowMetricPicker ?
|
|
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.ɵɵ
|
|
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.
|
|
410
|
+
i0.ɵɵconditional(ctx_r2.CanSave ? 37 : -1);
|
|
300
411
|
i0.ɵɵadvance();
|
|
301
|
-
i0.ɵɵconditional(ctx_r2.
|
|
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.
|
|
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, "
|
|
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,
|
|
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
|
|
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 }]
|