@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.
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
@@ -33,27 +33,29 @@ import * as i0 from "@angular/core";
33
33
  import * as i1 from "@angular/forms";
34
34
  import * as i2 from "@memberjunction/ng-entity-card";
35
35
  const _c0 = ["svgElement"];
36
+ const _forTrack0 = ($index, $item) => $item.ClusterId;
37
+ const _forTrack1 = ($index, $item) => $item.Name;
36
38
  function ClusterScatterComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
37
- i0.ɵɵelementStart(0, "div", 2)(1, "div", 8);
38
- i0.ɵɵelement(2, "i", 9);
39
+ i0.ɵɵelementStart(0, "div", 2)(1, "div", 9);
40
+ i0.ɵɵelement(2, "i", 10);
39
41
  i0.ɵɵelementStart(3, "span");
40
42
  i0.ɵɵtext(4, "Computing clusters...");
41
43
  i0.ɵɵelementEnd()()();
42
44
  } }
43
45
  function ClusterScatterComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
44
46
  i0.ɵɵelementStart(0, "div", 3);
45
- i0.ɵɵelement(1, "i", 10);
47
+ i0.ɵɵelement(1, "i", 11);
46
48
  i0.ɵɵelementStart(2, "p");
47
49
  i0.ɵɵtext(3, "No data to display");
48
50
  i0.ɵɵelementEnd();
49
- i0.ɵɵelementStart(4, "p", 11);
51
+ i0.ɵɵelementStart(4, "p", 12);
50
52
  i0.ɵɵtext(5, "Configure and run clustering to see results");
51
53
  i0.ɵɵelementEnd()();
52
54
  } }
53
55
  function ClusterScatterComponent_Conditional_3_For_4_Template(rf, ctx) { if (rf & 1) {
54
56
  i0.ɵɵnamespaceSVG();
55
- i0.ɵɵelementStart(0, "radialGradient", 13);
56
- i0.ɵɵelement(1, "stop", 16)(2, "stop", 17);
57
+ i0.ɵɵelementStart(0, "radialGradient", 14);
58
+ i0.ɵɵelement(1, "stop", 17)(2, "stop", 18);
57
59
  i0.ɵɵelementEnd();
58
60
  } if (rf & 2) {
59
61
  const centroid_r3 = ctx.$implicit;
@@ -77,16 +79,16 @@ function ClusterScatterComponent_Conditional_3_For_9_Template(rf, ctx) { if (rf
77
79
  const x_r5 = ctx.$implicit;
78
80
  i0.ɵɵattribute("x1", x_r5)("y1", 0)("x2", x_r5)("y2", 700);
79
81
  } }
80
- function ClusterScatterComponent_Conditional_3_For_11_Template(rf, ctx) { if (rf & 1) {
82
+ function ClusterScatterComponent_Conditional_3_Conditional_10_For_1_Template(rf, ctx) { if (rf & 1) {
81
83
  i0.ɵɵnamespaceSVG();
82
84
  i0.ɵɵelement(0, "circle");
83
85
  } if (rf & 2) {
84
86
  const centroid_r6 = ctx.$implicit;
85
87
  i0.ɵɵattribute("cx", centroid_r6.Cx)("cy", centroid_r6.Cy)("r", centroid_r6.R)("fill", "url(#cluster-glow-" + centroid_r6.ClusterId + ")");
86
88
  } }
87
- function ClusterScatterComponent_Conditional_3_For_13_Conditional_0_Template(rf, ctx) { if (rf & 1) {
89
+ function ClusterScatterComponent_Conditional_3_Conditional_10_For_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
88
90
  i0.ɵɵnamespaceSVG();
89
- i0.ɵɵelementStart(0, "text", 18);
91
+ i0.ɵɵelementStart(0, "text", 19);
90
92
  i0.ɵɵtext(1);
91
93
  i0.ɵɵelementEnd();
92
94
  } if (rf & 2) {
@@ -95,57 +97,91 @@ function ClusterScatterComponent_Conditional_3_For_13_Conditional_0_Template(rf,
95
97
  i0.ɵɵadvance();
96
98
  i0.ɵɵtextInterpolate(ctx);
97
99
  } }
98
- function ClusterScatterComponent_Conditional_3_For_13_Template(rf, ctx) { if (rf & 1) {
99
- i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_For_13_Conditional_0_Template, 2, 4, ":svg:text", 18);
100
+ function ClusterScatterComponent_Conditional_3_Conditional_10_For_3_Template(rf, ctx) { if (rf & 1) {
101
+ i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_Conditional_10_For_3_Conditional_0_Template, 2, 4, ":svg:text", 19);
100
102
  } if (rf & 2) {
101
- let tmp_12_0;
103
+ let tmp_13_0;
102
104
  const centroid_r7 = ctx.$implicit;
105
+ const ctx_r1 = i0.ɵɵnextContext(3);
106
+ i0.ɵɵconditional((tmp_13_0 = ctx_r1.GetClusterLabelById(centroid_r7.ClusterId)) ? 0 : -1, tmp_13_0);
107
+ } }
108
+ function ClusterScatterComponent_Conditional_3_Conditional_10_Template(rf, ctx) { if (rf & 1) {
109
+ i0.ɵɵrepeaterCreate(0, ClusterScatterComponent_Conditional_3_Conditional_10_For_1_Template, 1, 4, ":svg:circle", null, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
110
+ i0.ɵɵrepeaterCreate(2, ClusterScatterComponent_Conditional_3_Conditional_10_For_3_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
111
+ } if (rf & 2) {
103
112
  const ctx_r1 = i0.ɵɵnextContext(2);
104
- i0.ɵɵconditional((tmp_12_0 = ctx_r1.GetClusterLabelById(centroid_r7.ClusterId)) ? 0 : -1, tmp_12_0);
113
+ i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
114
+ i0.ɵɵadvance(2);
115
+ i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
105
116
  } }
106
- function ClusterScatterComponent_Conditional_3_For_15_Template(rf, ctx) { if (rf & 1) {
117
+ function ClusterScatterComponent_Conditional_3_For_12_Template(rf, ctx) { if (rf & 1) {
107
118
  const _r8 = i0.ɵɵgetCurrentView();
108
119
  i0.ɵɵnamespaceSVG();
109
- i0.ɵɵelementStart(0, "circle", 19);
110
- i0.ɵɵlistener("mouseenter", function ClusterScatterComponent_Conditional_3_For_15_Template_circle_mouseenter_0_listener($event) { const point_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPointMouseEnter(point_r9, $event)); })("mouseleave", function ClusterScatterComponent_Conditional_3_For_15_Template_circle_mouseleave_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPointMouseLeave()); })("click", function ClusterScatterComponent_Conditional_3_For_15_Template_circle_click_0_listener($event) { const point_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.OnPointClick(point_r9); return i0.ɵɵresetView($event.stopPropagation()); });
120
+ i0.ɵɵelementStart(0, "circle", 20);
121
+ i0.ɵɵlistener("mouseenter", function ClusterScatterComponent_Conditional_3_For_12_Template_circle_mouseenter_0_listener($event) { const point_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPointMouseEnter(point_r9, $event)); })("mouseleave", function ClusterScatterComponent_Conditional_3_For_12_Template_circle_mouseleave_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPointMouseLeave()); })("click", function ClusterScatterComponent_Conditional_3_For_12_Template_circle_click_0_listener($event) { const point_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); ctx_r1.OnPointClick(point_r9); return i0.ɵɵresetView($event.stopPropagation()); });
111
122
  i0.ɵɵelementEnd();
112
123
  } if (rf & 2) {
113
124
  const point_r9 = ctx.$implicit;
114
125
  const ctx_r1 = i0.ɵɵnextContext(2);
115
- i0.ɵɵattribute("cx", point_r9.X)("cy", point_r9.Y)("r", ctx_r1.GetPointRadius(point_r9))("fill", ctx_r1.GetPointColor(point_r9))("opacity", ctx_r1.GetPointOpacity(point_r9));
126
+ i0.ɵɵattribute("cx", ctx_r1.PX(point_r9))("cy", ctx_r1.PY(point_r9))("r", ctx_r1.GetPointRadius(point_r9))("fill", ctx_r1.GetPointColor(point_r9))("opacity", ctx_r1.GetPointOpacity(point_r9));
116
127
  } }
117
- function ClusterScatterComponent_Conditional_3_For_17_Conditional_0_Template(rf, ctx) { if (rf & 1) {
128
+ function ClusterScatterComponent_Conditional_3_Conditional_13_For_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
118
129
  i0.ɵɵnamespaceSVG();
119
- i0.ɵɵelement(0, "circle", 20);
130
+ i0.ɵɵelementStart(0, "text", 21);
131
+ i0.ɵɵtext(1);
132
+ i0.ɵɵelementEnd();
120
133
  } if (rf & 2) {
121
- const point_r10 = i0.ɵɵnextContext().$implicit;
122
- const ctx_r1 = i0.ɵɵnextContext(2);
123
- i0.ɵɵattribute("cx", point_r10.X)("cy", point_r10.Y)("r", ctx_r1.HighlightRadius + 2)("stroke", ctx_r1.GetPointColor(point_r10));
134
+ const lbl_r10 = i0.ɵɵnextContext().$implicit;
135
+ i0.ɵɵattribute("x", lbl_r10.X)("y", lbl_r10.Y)("fill", lbl_r10.Color);
136
+ i0.ɵɵadvance();
137
+ i0.ɵɵtextInterpolate(ctx);
124
138
  } }
125
- function ClusterScatterComponent_Conditional_3_For_17_Template(rf, ctx) { if (rf & 1) {
126
- i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_For_17_Conditional_0_Template, 1, 4, ":svg:circle", 20);
139
+ function ClusterScatterComponent_Conditional_3_Conditional_13_For_1_Template(rf, ctx) { if (rf & 1) {
140
+ i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_Conditional_13_For_1_Conditional_0_Template, 2, 4, ":svg:text", 21);
141
+ } if (rf & 2) {
142
+ let tmp_13_0;
143
+ const lbl_r10 = ctx.$implicit;
144
+ const ctx_r1 = i0.ɵɵnextContext(3);
145
+ i0.ɵɵconditional((tmp_13_0 = ctx_r1.GetClusterLabelById(lbl_r10.ClusterId)) ? 0 : -1, tmp_13_0);
146
+ } }
147
+ function ClusterScatterComponent_Conditional_3_Conditional_13_Template(rf, ctx) { if (rf & 1) {
148
+ i0.ɵɵrepeaterCreate(0, ClusterScatterComponent_Conditional_3_Conditional_13_For_1_Template, 1, 1, null, null, _forTrack0);
127
149
  } if (rf & 2) {
128
- const point_r10 = ctx.$implicit;
129
150
  const ctx_r1 = i0.ɵɵnextContext(2);
130
- i0.ɵɵconditional(ctx_r1.IsSelected(point_r10) ? 0 : -1);
151
+ i0.ɵɵrepeater(ctx_r1.ProjectedClusterLabels);
131
152
  } }
132
- function ClusterScatterComponent_Conditional_3_Conditional_18_For_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
153
+ function ClusterScatterComponent_Conditional_3_For_15_Conditional_0_Template(rf, ctx) { if (rf & 1) {
133
154
  i0.ɵɵnamespaceSVG();
134
- i0.ɵɵelement(0, "circle", 21);
155
+ i0.ɵɵelement(0, "circle", 22);
135
156
  } if (rf & 2) {
136
157
  const point_r11 = i0.ɵɵnextContext().$implicit;
137
- const ctx_r1 = i0.ɵɵnextContext(3);
138
- i0.ɵɵattribute("cx", point_r11.X)("cy", point_r11.Y)("r", ctx_r1.HighlightRadius + 4)("stroke", ctx_r1.GetPointColor(point_r11));
158
+ const ctx_r1 = i0.ɵɵnextContext(2);
159
+ i0.ɵɵattribute("cx", ctx_r1.PX(point_r11))("cy", ctx_r1.PY(point_r11))("r", ctx_r1.HighlightRadius + 2)("stroke", ctx_r1.GetPointColor(point_r11));
139
160
  } }
140
- function ClusterScatterComponent_Conditional_3_Conditional_18_For_1_Template(rf, ctx) { if (rf & 1) {
141
- i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_Conditional_18_For_1_Conditional_0_Template, 1, 4, ":svg:circle", 21);
161
+ function ClusterScatterComponent_Conditional_3_For_15_Template(rf, ctx) { if (rf & 1) {
162
+ i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_For_15_Conditional_0_Template, 1, 4, ":svg:circle", 22);
142
163
  } if (rf & 2) {
143
164
  const point_r11 = ctx.$implicit;
165
+ const ctx_r1 = i0.ɵɵnextContext(2);
166
+ i0.ɵɵconditional(ctx_r1.IsSelected(point_r11) ? 0 : -1);
167
+ } }
168
+ function ClusterScatterComponent_Conditional_3_Conditional_16_For_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
169
+ i0.ɵɵnamespaceSVG();
170
+ i0.ɵɵelement(0, "circle", 23);
171
+ } if (rf & 2) {
172
+ const point_r12 = i0.ɵɵnextContext().$implicit;
173
+ const ctx_r1 = i0.ɵɵnextContext(3);
174
+ i0.ɵɵattribute("cx", ctx_r1.PX(point_r12))("cy", ctx_r1.PY(point_r12))("r", ctx_r1.HighlightRadius + 4)("stroke", ctx_r1.GetPointColor(point_r12));
175
+ } }
176
+ function ClusterScatterComponent_Conditional_3_Conditional_16_For_1_Template(rf, ctx) { if (rf & 1) {
177
+ i0.ɵɵconditionalCreate(0, ClusterScatterComponent_Conditional_3_Conditional_16_For_1_Conditional_0_Template, 1, 4, ":svg:circle", 23);
178
+ } if (rf & 2) {
179
+ const point_r12 = ctx.$implicit;
144
180
  const ctx_r1 = i0.ɵɵnextContext(3);
145
- i0.ɵɵconditional(ctx_r1.IsHighlighted(point_r11) ? 0 : -1);
181
+ i0.ɵɵconditional(ctx_r1.IsHighlighted(point_r12) ? 0 : -1);
146
182
  } }
147
- function ClusterScatterComponent_Conditional_3_Conditional_18_Template(rf, ctx) { if (rf & 1) {
148
- i0.ɵɵrepeaterCreate(0, ClusterScatterComponent_Conditional_3_Conditional_18_For_1_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackPointBy, true);
183
+ function ClusterScatterComponent_Conditional_3_Conditional_16_Template(rf, ctx) { if (rf & 1) {
184
+ i0.ɵɵrepeaterCreate(0, ClusterScatterComponent_Conditional_3_Conditional_16_For_1_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackPointBy, true);
149
185
  } if (rf & 2) {
150
186
  const ctx_r1 = i0.ɵɵnextContext(2);
151
187
  i0.ɵɵrepeater(ctx_r1.Points);
@@ -153,20 +189,20 @@ function ClusterScatterComponent_Conditional_3_Conditional_18_Template(rf, ctx)
153
189
  function ClusterScatterComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
154
190
  const _r1 = i0.ɵɵgetCurrentView();
155
191
  i0.ɵɵnamespaceSVG();
156
- i0.ɵɵelementStart(0, "svg", 12, 0);
192
+ i0.ɵɵelementStart(0, "svg", 13, 0);
157
193
  i0.ɵɵlistener("wheel", function ClusterScatterComponent_Conditional_3_Template_svg_wheel_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnWheel($event)); })("mousedown", function ClusterScatterComponent_Conditional_3_Template_svg_mousedown_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnMouseDown($event)); });
158
194
  i0.ɵɵelementStart(2, "defs");
159
- i0.ɵɵrepeaterCreate(3, ClusterScatterComponent_Conditional_3_For_4_Template, 3, 3, ":svg:radialGradient", 13, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
195
+ i0.ɵɵrepeaterCreate(3, ClusterScatterComponent_Conditional_3_For_4_Template, 3, 3, ":svg:radialGradient", 14, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
160
196
  i0.ɵɵelementEnd();
161
- i0.ɵɵelementStart(5, "g", 14);
197
+ i0.ɵɵelementStart(5, "g", 15);
162
198
  i0.ɵɵrepeaterCreate(6, ClusterScatterComponent_Conditional_3_For_7_Template, 1, 4, ":svg:line", null, i0.ɵɵrepeaterTrackByIdentity);
163
199
  i0.ɵɵrepeaterCreate(8, ClusterScatterComponent_Conditional_3_For_9_Template, 1, 4, ":svg:line", null, i0.ɵɵrepeaterTrackByIdentity);
164
200
  i0.ɵɵelementEnd();
165
- i0.ɵɵrepeaterCreate(10, ClusterScatterComponent_Conditional_3_For_11_Template, 1, 4, ":svg:circle", null, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
166
- i0.ɵɵrepeaterCreate(12, ClusterScatterComponent_Conditional_3_For_13_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackCentroidBy, true);
167
- i0.ɵɵrepeaterCreate(14, ClusterScatterComponent_Conditional_3_For_15_Template, 1, 5, ":svg:circle", 15, i0.ɵɵcomponentInstance().TrackPointBy, true);
168
- i0.ɵɵrepeaterCreate(16, ClusterScatterComponent_Conditional_3_For_17_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackPointBy, true);
169
- i0.ɵɵconditionalCreate(18, ClusterScatterComponent_Conditional_3_Conditional_18_Template, 2, 0);
201
+ i0.ɵɵconditionalCreate(10, ClusterScatterComponent_Conditional_3_Conditional_10_Template, 4, 0);
202
+ i0.ɵɵrepeaterCreate(11, ClusterScatterComponent_Conditional_3_For_12_Template, 1, 5, ":svg:circle", 16, i0.ɵɵcomponentInstance().TrackPointBy, true);
203
+ i0.ɵɵconditionalCreate(13, ClusterScatterComponent_Conditional_3_Conditional_13_Template, 2, 0);
204
+ i0.ɵɵrepeaterCreate(14, ClusterScatterComponent_Conditional_3_For_15_Template, 1, 1, null, null, i0.ɵɵcomponentInstance().TrackPointBy, true);
205
+ i0.ɵɵconditionalCreate(16, ClusterScatterComponent_Conditional_3_Conditional_16_Template, 2, 0);
170
206
  i0.ɵɵelementEnd();
171
207
  } if (rf & 2) {
172
208
  const ctx_r1 = i0.ɵɵnextContext();
@@ -179,79 +215,120 @@ function ClusterScatterComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
179
215
  i0.ɵɵadvance(2);
180
216
  i0.ɵɵrepeater(ctx_r1.VerticalGridLines);
181
217
  i0.ɵɵadvance(2);
182
- i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
183
- i0.ɵɵadvance(2);
184
- i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
218
+ i0.ɵɵconditional(!ctx_r1.Is3D ? 10 : -1);
219
+ i0.ɵɵadvance();
220
+ i0.ɵɵrepeater(ctx_r1.RenderPoints);
185
221
  i0.ɵɵadvance(2);
222
+ i0.ɵɵconditional(ctx_r1.Is3D && ctx_r1.ColorBy !== "entity" ? 13 : -1);
223
+ i0.ɵɵadvance();
186
224
  i0.ɵɵrepeater(ctx_r1.Points);
187
225
  i0.ɵɵadvance(2);
188
- i0.ɵɵrepeater(ctx_r1.Points);
226
+ i0.ɵɵconditional(ctx_r1.HighlightedKey ? 16 : -1);
227
+ } }
228
+ function ClusterScatterComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
229
+ const _r13 = i0.ɵɵgetCurrentView();
230
+ i0.ɵɵelementStart(0, "div", 5);
231
+ i0.ɵɵelement(1, "i", 24);
232
+ i0.ɵɵelementStart(2, "span");
233
+ i0.ɵɵtext(3, "3D \u00B7 drag to rotate");
234
+ i0.ɵɵelementEnd();
235
+ i0.ɵɵelementStart(4, "button", 25);
236
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_4_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r13); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ResetRotation()); });
237
+ i0.ɵɵelement(5, "i", 26);
238
+ i0.ɵɵelementEnd()();
239
+ } }
240
+ function ClusterScatterComponent_Conditional_5_For_4_Template(rf, ctx) { if (rf & 1) {
241
+ i0.ɵɵelementStart(0, "div", 28);
242
+ i0.ɵɵelement(1, "div", 29);
243
+ i0.ɵɵelementStart(2, "span", 30);
244
+ i0.ɵɵtext(3);
245
+ i0.ɵɵelementEnd();
246
+ i0.ɵɵelementStart(4, "span", 31);
247
+ i0.ɵɵtext(5);
248
+ i0.ɵɵelementEnd()();
249
+ } if (rf & 2) {
250
+ const ent_r14 = ctx.$implicit;
251
+ i0.ɵɵadvance();
252
+ i0.ɵɵstyleProp("background", ent_r14.Color);
253
+ i0.ɵɵadvance(2);
254
+ i0.ɵɵtextInterpolate(ent_r14.Name);
189
255
  i0.ɵɵadvance(2);
190
- i0.ɵɵconditional(ctx_r1.HighlightedKey ? 18 : -1);
256
+ i0.ɵɵtextInterpolate(ent_r14.Count);
191
257
  } }
192
- function ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
193
- const _r14 = i0.ɵɵgetCurrentView();
194
- i0.ɵɵelementStart(0, "input", 28);
195
- i0.ɵɵtwoWayListener("ngModelChange", function ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template_input_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r14); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.EditingLabelDraft, $event) || (ctx_r1.EditingLabelDraft = $event); return i0.ɵɵresetView($event); });
196
- i0.ɵɵlistener("keydown", function ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template_input_keydown_0_listener($event) { i0.ɵɵrestoreView(_r14); const cluster_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnLabelEditKeydown($event, cluster_r13)); })("blur", function ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template_input_blur_0_listener() { i0.ɵɵrestoreView(_r14); const cluster_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.CommitLabelEdit(cluster_r13)); })("click", function ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template_input_click_0_listener($event) { i0.ɵɵrestoreView(_r14); return i0.ɵɵresetView($event.stopPropagation()); });
258
+ function ClusterScatterComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
259
+ i0.ɵɵelementStart(0, "div", 6)(1, "div", 27);
260
+ i0.ɵɵtext(2, "Entities");
261
+ i0.ɵɵelementEnd();
262
+ i0.ɵɵrepeaterCreate(3, ClusterScatterComponent_Conditional_5_For_4_Template, 6, 4, "div", 28, _forTrack1);
263
+ i0.ɵɵelementEnd();
264
+ } if (rf & 2) {
265
+ const ctx_r1 = i0.ɵɵnextContext();
266
+ i0.ɵɵadvance(3);
267
+ i0.ɵɵrepeater(ctx_r1.EntityLegend);
268
+ } }
269
+ function ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
270
+ const _r17 = i0.ɵɵgetCurrentView();
271
+ i0.ɵɵelementStart(0, "input", 34);
272
+ i0.ɵɵtwoWayListener("ngModelChange", function ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template_input_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.EditingLabelDraft, $event) || (ctx_r1.EditingLabelDraft = $event); return i0.ɵɵresetView($event); });
273
+ i0.ɵɵlistener("keydown", function ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template_input_keydown_0_listener($event) { i0.ɵɵrestoreView(_r17); const cluster_r16 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnLabelEditKeydown($event, cluster_r16)); })("blur", function ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template_input_blur_0_listener() { i0.ɵɵrestoreView(_r17); const cluster_r16 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.CommitLabelEdit(cluster_r16)); })("click", function ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template_input_click_0_listener($event) { i0.ɵɵrestoreView(_r17); return i0.ɵɵresetView($event.stopPropagation()); });
197
274
  i0.ɵɵelementEnd();
198
275
  } if (rf & 2) {
199
276
  const ctx_r1 = i0.ɵɵnextContext(3);
200
277
  i0.ɵɵtwoWayProperty("ngModel", ctx_r1.EditingLabelDraft);
201
278
  } }
202
- function ClusterScatterComponent_Conditional_4_For_4_Conditional_3_Template(rf, ctx) { if (rf & 1) {
203
- const _r15 = i0.ɵɵgetCurrentView();
204
- i0.ɵɵelementStart(0, "span", 29);
279
+ function ClusterScatterComponent_Conditional_6_For_4_Conditional_3_Template(rf, ctx) { if (rf & 1) {
280
+ const _r18 = i0.ɵɵgetCurrentView();
281
+ i0.ɵɵelementStart(0, "span", 30);
205
282
  i0.ɵɵtext(1);
206
283
  i0.ɵɵelementEnd();
207
- i0.ɵɵelementStart(2, "button", 30);
208
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_4_For_4_Conditional_3_Template_button_click_2_listener($event) { i0.ɵɵrestoreView(_r15); const cluster_r13 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.StartLabelEdit(cluster_r13, $event)); });
209
- i0.ɵɵelement(3, "i", 31);
284
+ i0.ɵɵelementStart(2, "button", 35);
285
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_6_For_4_Conditional_3_Template_button_click_2_listener($event) { i0.ɵɵrestoreView(_r18); const cluster_r16 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.StartLabelEdit(cluster_r16, $event)); });
286
+ i0.ɵɵelement(3, "i", 36);
210
287
  i0.ɵɵelementEnd();
211
288
  } if (rf & 2) {
212
- const cluster_r13 = i0.ɵɵnextContext().$implicit;
289
+ const cluster_r16 = i0.ɵɵnextContext().$implicit;
213
290
  i0.ɵɵadvance();
214
- i0.ɵɵtextInterpolate(cluster_r13.Label);
291
+ i0.ɵɵtextInterpolate(cluster_r16.Label);
215
292
  } }
216
- function ClusterScatterComponent_Conditional_4_For_4_Template(rf, ctx) { if (rf & 1) {
217
- const _r12 = i0.ɵɵgetCurrentView();
218
- i0.ɵɵelementStart(0, "div", 24);
219
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_4_For_4_Template_div_click_0_listener() { const cluster_r13 = i0.ɵɵrestoreView(_r12).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnClusterLegendClick(cluster_r13)); });
220
- i0.ɵɵelement(1, "div", 25);
221
- i0.ɵɵconditionalCreate(2, ClusterScatterComponent_Conditional_4_For_4_Conditional_2_Template, 1, 1, "input", 26)(3, ClusterScatterComponent_Conditional_4_For_4_Conditional_3_Template, 4, 1);
222
- i0.ɵɵelementStart(4, "span", 27);
293
+ function ClusterScatterComponent_Conditional_6_For_4_Template(rf, ctx) { if (rf & 1) {
294
+ const _r15 = i0.ɵɵgetCurrentView();
295
+ i0.ɵɵelementStart(0, "div", 32);
296
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_6_For_4_Template_div_click_0_listener() { const cluster_r16 = i0.ɵɵrestoreView(_r15).$implicit; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnClusterLegendClick(cluster_r16)); });
297
+ i0.ɵɵelement(1, "div", 29);
298
+ i0.ɵɵconditionalCreate(2, ClusterScatterComponent_Conditional_6_For_4_Conditional_2_Template, 1, 1, "input", 33)(3, ClusterScatterComponent_Conditional_6_For_4_Conditional_3_Template, 4, 1);
299
+ i0.ɵɵelementStart(4, "span", 31);
223
300
  i0.ɵɵtext(5);
224
301
  i0.ɵɵelementEnd()();
225
302
  } if (rf & 2) {
226
- const cluster_r13 = ctx.$implicit;
303
+ const cluster_r16 = ctx.$implicit;
227
304
  const ctx_r1 = i0.ɵɵnextContext(2);
228
305
  i0.ɵɵadvance();
229
- i0.ɵɵstyleProp("background", cluster_r13.Color);
306
+ i0.ɵɵstyleProp("background", cluster_r16.Color);
230
307
  i0.ɵɵadvance();
231
- i0.ɵɵconditional(ctx_r1.EditingClusterId === cluster_r13.Id ? 2 : 3);
308
+ i0.ɵɵconditional(ctx_r1.EditingClusterId === cluster_r16.Id ? 2 : 3);
232
309
  i0.ɵɵadvance(3);
233
- i0.ɵɵtextInterpolate(cluster_r13.MemberCount);
310
+ i0.ɵɵtextInterpolate(cluster_r16.MemberCount);
234
311
  } }
235
- function ClusterScatterComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
236
- i0.ɵɵelementStart(0, "div", 5)(1, "div", 22);
312
+ function ClusterScatterComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
313
+ i0.ɵɵelementStart(0, "div", 6)(1, "div", 27);
237
314
  i0.ɵɵtext(2, "Clusters");
238
315
  i0.ɵɵelementEnd();
239
- i0.ɵɵrepeaterCreate(3, ClusterScatterComponent_Conditional_4_For_4_Template, 6, 4, "div", 23, i0.ɵɵcomponentInstance().TrackClusterBy, true);
316
+ i0.ɵɵrepeaterCreate(3, ClusterScatterComponent_Conditional_6_For_4_Template, 6, 4, "div", 28, i0.ɵɵcomponentInstance().TrackClusterBy, true);
240
317
  i0.ɵɵelementEnd();
241
318
  } if (rf & 2) {
242
319
  const ctx_r1 = i0.ɵɵnextContext();
243
320
  i0.ɵɵadvance(3);
244
321
  i0.ɵɵrepeater(ctx_r1.Clusters);
245
322
  } }
246
- function ClusterScatterComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
247
- i0.ɵɵelementStart(0, "div", 32);
248
- i0.ɵɵelement(1, "mj-entity-card", 33);
249
- i0.ɵɵelementStart(2, "div", 34);
250
- i0.ɵɵelement(3, "i", 35);
323
+ function ClusterScatterComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
324
+ i0.ɵɵelementStart(0, "div", 37);
325
+ i0.ɵɵelement(1, "mj-entity-card", 38);
326
+ i0.ɵɵelementStart(2, "div", 39);
327
+ i0.ɵɵelement(3, "i", 40);
251
328
  i0.ɵɵtext(4);
252
329
  i0.ɵɵelementEnd();
253
- i0.ɵɵelementStart(5, "div", 36);
254
- i0.ɵɵelement(6, "i", 37);
330
+ i0.ɵɵelementStart(5, "div", 41);
331
+ i0.ɵɵelement(6, "i", 42);
255
332
  i0.ɵɵtext(7, " Click to view details ");
256
333
  i0.ɵɵelementEnd()();
257
334
  } if (rf & 2) {
@@ -262,8 +339,8 @@ function ClusterScatterComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
262
339
  i0.ɵɵadvance(3);
263
340
  i0.ɵɵtextInterpolate1(" ", ctx_r1.GetClusterLabel(ctx_r1.HoveredPoint), " ");
264
341
  } }
265
- function ClusterScatterComponent_Conditional_7_Conditional_2_Template(rf, ctx) { if (rf & 1) {
266
- i0.ɵɵelementStart(0, "div", 40);
342
+ function ClusterScatterComponent_Conditional_9_Conditional_2_Template(rf, ctx) { if (rf & 1) {
343
+ i0.ɵɵelementStart(0, "div", 45);
267
344
  i0.ɵɵelement(1, "i");
268
345
  i0.ɵɵelementEnd();
269
346
  } if (rf & 2) {
@@ -271,8 +348,8 @@ function ClusterScatterComponent_Conditional_7_Conditional_2_Template(rf, ctx) {
271
348
  i0.ɵɵadvance();
272
349
  i0.ɵɵclassMap(ctx_r1.SelectedPoint.Metadata["EntityIcon"]);
273
350
  } }
274
- function ClusterScatterComponent_Conditional_7_Conditional_6_Template(rf, ctx) { if (rf & 1) {
275
- i0.ɵɵelementStart(0, "span", 43);
351
+ function ClusterScatterComponent_Conditional_9_Conditional_6_Template(rf, ctx) { if (rf & 1) {
352
+ i0.ɵɵelementStart(0, "span", 48);
276
353
  i0.ɵɵtext(1);
277
354
  i0.ɵɵelementEnd();
278
355
  } if (rf & 2) {
@@ -280,70 +357,70 @@ function ClusterScatterComponent_Conditional_7_Conditional_6_Template(rf, ctx) {
280
357
  i0.ɵɵadvance();
281
358
  i0.ɵɵtextInterpolate(ctx_r1.SelectedPoint.Metadata["Entity"]);
282
359
  } }
283
- function ClusterScatterComponent_Conditional_7_Conditional_20_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
284
- i0.ɵɵelement(0, "i", 60);
360
+ function ClusterScatterComponent_Conditional_9_Conditional_20_For_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
361
+ i0.ɵɵelement(0, "i", 65);
285
362
  } if (rf & 2) {
286
- const member_r18 = i0.ɵɵnextContext().$implicit;
287
- i0.ɵɵclassMap(member_r18.Metadata["EntityIcon"]);
363
+ const member_r21 = i0.ɵɵnextContext().$implicit;
364
+ i0.ɵɵclassMap(member_r21.Metadata["EntityIcon"]);
288
365
  } }
289
- function ClusterScatterComponent_Conditional_7_Conditional_20_For_2_Template(rf, ctx) { if (rf & 1) {
290
- const _r17 = i0.ɵɵgetCurrentView();
291
- i0.ɵɵelementStart(0, "div", 57);
292
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_7_Conditional_20_For_2_Template_div_click_0_listener() { const member_r18 = i0.ɵɵrestoreView(_r17).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnClusterMemberClick(member_r18)); });
293
- i0.ɵɵconditionalCreate(1, ClusterScatterComponent_Conditional_7_Conditional_20_For_2_Conditional_1_Template, 1, 2, "i", 58);
294
- i0.ɵɵelementStart(2, "span", 59);
366
+ function ClusterScatterComponent_Conditional_9_Conditional_20_For_2_Template(rf, ctx) { if (rf & 1) {
367
+ const _r20 = i0.ɵɵgetCurrentView();
368
+ i0.ɵɵelementStart(0, "div", 62);
369
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_9_Conditional_20_For_2_Template_div_click_0_listener() { const member_r21 = i0.ɵɵrestoreView(_r20).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnClusterMemberClick(member_r21)); });
370
+ i0.ɵɵconditionalCreate(1, ClusterScatterComponent_Conditional_9_Conditional_20_For_2_Conditional_1_Template, 1, 2, "i", 63);
371
+ i0.ɵɵelementStart(2, "span", 64);
295
372
  i0.ɵɵtext(3);
296
373
  i0.ɵɵelementEnd()();
297
374
  } if (rf & 2) {
298
- const member_r18 = ctx.$implicit;
375
+ const member_r21 = ctx.$implicit;
299
376
  const ctx_r1 = i0.ɵɵnextContext(3);
300
- i0.ɵɵclassProp("active", member_r18.VectorKey === ctx_r1.SelectedPoint.VectorKey);
377
+ i0.ɵɵclassProp("active", member_r21.VectorKey === ctx_r1.SelectedPoint.VectorKey);
301
378
  i0.ɵɵadvance();
302
- i0.ɵɵconditional((member_r18.Metadata == null ? null : member_r18.Metadata["EntityIcon"]) ? 1 : -1);
379
+ i0.ɵɵconditional((member_r21.Metadata == null ? null : member_r21.Metadata["EntityIcon"]) ? 1 : -1);
303
380
  i0.ɵɵadvance(2);
304
- i0.ɵɵtextInterpolate(member_r18.Label);
381
+ i0.ɵɵtextInterpolate(member_r21.Label);
305
382
  } }
306
- function ClusterScatterComponent_Conditional_7_Conditional_20_Template(rf, ctx) { if (rf & 1) {
307
- i0.ɵɵelementStart(0, "div", 53);
308
- i0.ɵɵrepeaterCreate(1, ClusterScatterComponent_Conditional_7_Conditional_20_For_2_Template, 4, 4, "div", 56, i0.ɵɵcomponentInstance().TrackMemberBy, true);
383
+ function ClusterScatterComponent_Conditional_9_Conditional_20_Template(rf, ctx) { if (rf & 1) {
384
+ i0.ɵɵelementStart(0, "div", 58);
385
+ i0.ɵɵrepeaterCreate(1, ClusterScatterComponent_Conditional_9_Conditional_20_For_2_Template, 4, 4, "div", 61, i0.ɵɵcomponentInstance().TrackMemberBy, true);
309
386
  i0.ɵɵelementEnd();
310
387
  } if (rf & 2) {
311
388
  const ctx_r1 = i0.ɵɵnextContext(2);
312
389
  i0.ɵɵadvance();
313
390
  i0.ɵɵrepeater(ctx_r1.ClusterMembers);
314
391
  } }
315
- function ClusterScatterComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
316
- const _r16 = i0.ɵɵgetCurrentView();
317
- i0.ɵɵelementStart(0, "div", 38)(1, "div", 39);
318
- i0.ɵɵconditionalCreate(2, ClusterScatterComponent_Conditional_7_Conditional_2_Template, 2, 2, "div", 40);
319
- i0.ɵɵelementStart(3, "div", 41)(4, "div", 42);
392
+ function ClusterScatterComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
393
+ const _r19 = i0.ɵɵgetCurrentView();
394
+ i0.ɵɵelementStart(0, "div", 43)(1, "div", 44);
395
+ i0.ɵɵconditionalCreate(2, ClusterScatterComponent_Conditional_9_Conditional_2_Template, 2, 2, "div", 45);
396
+ i0.ɵɵelementStart(3, "div", 46)(4, "div", 47);
320
397
  i0.ɵɵtext(5);
321
398
  i0.ɵɵelementEnd();
322
- i0.ɵɵconditionalCreate(6, ClusterScatterComponent_Conditional_7_Conditional_6_Template, 2, 1, "span", 43);
399
+ i0.ɵɵconditionalCreate(6, ClusterScatterComponent_Conditional_9_Conditional_6_Template, 2, 1, "span", 48);
323
400
  i0.ɵɵelementEnd()();
324
- i0.ɵɵelementStart(7, "button", 44);
325
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_7_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseDetailPanel()); });
326
- i0.ɵɵelement(8, "i", 45);
401
+ i0.ɵɵelementStart(7, "button", 49);
402
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_9_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.CloseDetailPanel()); });
403
+ i0.ɵɵelement(8, "i", 50);
327
404
  i0.ɵɵelementEnd()();
328
- i0.ɵɵelementStart(9, "div", 46);
329
- i0.ɵɵelement(10, "span", 47);
405
+ i0.ɵɵelementStart(9, "div", 51);
406
+ i0.ɵɵelement(10, "span", 52);
330
407
  i0.ɵɵtext(11);
331
408
  i0.ɵɵelementEnd();
332
- i0.ɵɵelement(12, "mj-entity-card", 48);
333
- i0.ɵɵelementStart(13, "div", 49)(14, "button", 50);
334
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_7_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ToggleClusterMembers()); });
335
- i0.ɵɵelement(15, "i", 51);
409
+ i0.ɵɵelement(12, "mj-entity-card", 53);
410
+ i0.ɵɵelementStart(13, "div", 54)(14, "button", 55);
411
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_9_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ToggleClusterMembers()); });
412
+ i0.ɵɵelement(15, "i", 56);
336
413
  i0.ɵɵelementStart(16, "span");
337
414
  i0.ɵɵtext(17, "Cluster Members");
338
415
  i0.ɵɵelementEnd();
339
- i0.ɵɵelementStart(18, "span", 52);
416
+ i0.ɵɵelementStart(18, "span", 57);
340
417
  i0.ɵɵtext(19);
341
418
  i0.ɵɵelementEnd()();
342
- i0.ɵɵconditionalCreate(20, ClusterScatterComponent_Conditional_7_Conditional_20_Template, 3, 0, "div", 53);
419
+ i0.ɵɵconditionalCreate(20, ClusterScatterComponent_Conditional_9_Conditional_20_Template, 3, 0, "div", 58);
343
420
  i0.ɵɵelementEnd();
344
- i0.ɵɵelementStart(21, "div", 54)(22, "button", 55);
345
- i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_7_Template_button_click_22_listener() { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnOpenRecordClick()); });
346
- i0.ɵɵelement(23, "i", 37);
421
+ i0.ɵɵelementStart(21, "div", 59)(22, "button", 60);
422
+ i0.ɵɵlistener("click", function ClusterScatterComponent_Conditional_9_Template_button_click_22_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnOpenRecordClick()); });
423
+ i0.ɵɵelement(23, "i", 42);
347
424
  i0.ɵɵtext(24, " Open Record ");
348
425
  i0.ɵɵelementEnd()();
349
426
  } if (rf & 2) {
@@ -384,6 +461,12 @@ export class ClusterScatterComponent {
384
461
  * Cluster summary information used for the legend and color mapping.
385
462
  */
386
463
  this.Clusters = [];
464
+ /**
465
+ * Legend / color mode. `'cluster'` (default) colors each point by its assigned
466
+ * cluster; `'entity'` colors by the point's source entity (`Metadata.EntityName`),
467
+ * which is the useful mode for multi-entity analyses.
468
+ */
469
+ this.ColorBy = 'cluster';
387
470
  /**
388
471
  * When `true`, a semi-transparent loading overlay is displayed
389
472
  * on top of the scatter plot.
@@ -652,11 +735,25 @@ export class ClusterScatterComponent {
652
735
  this.panStartX = 0;
653
736
  this.panStartY = 0;
654
737
  this.panStartViewBox = [0, 0, 1000, 700];
738
+ // 3D orbit-drag state
739
+ this.isRotating = false;
740
+ this.rotateStartX = 0;
741
+ this.rotateStartY = 0;
742
+ this.rotateStartYaw = 0;
743
+ this.rotateStartPitch = 0;
655
744
  this.boundOnMouseMove = null;
656
745
  this.boundOnMouseUp = null;
657
746
  /** Default viewBox dimensions used for zoom-limit calculations. */
658
747
  this.defaultWidth = 1000;
659
748
  this.defaultHeight = 700;
749
+ // ================================================================
750
+ // Color-by-entity (multi-entity analyses)
751
+ // ================================================================
752
+ /** Stable entity-name → color map, assigned in first-seen order. */
753
+ this.entityColorMap = new Map();
754
+ /** Orbit angles (radians) for the interactive 3D projection. Updated by drag. */
755
+ this.Yaw = 0.6;
756
+ this.Pitch = 0.45;
660
757
  }
661
758
  /** Start inline editing for a cluster label. */
662
759
  StartLabelEdit(cluster, event) {
@@ -773,12 +870,127 @@ export class ClusterScatterComponent {
773
870
  * @returns CSS color string.
774
871
  */
775
872
  GetPointColor(point) {
873
+ if (this.ColorBy === 'entity') {
874
+ return this.getEntityColor(point);
875
+ }
776
876
  if (point.ClusterId < 0)
777
877
  return '#64748b'; // outlier gray
778
878
  const palette = this.getActivePalette();
779
879
  const cluster = this.Clusters.find(c => c.Id === point.ClusterId);
780
880
  return cluster?.Color ?? palette[point.ClusterId % palette.length];
781
881
  }
882
+ /** Resolve a color for a point keyed by its source entity name. */
883
+ getEntityColor(point) {
884
+ const name = point.Metadata?.['EntityName'] || point.Metadata?.['Entity'] || 'Unknown';
885
+ let color = this.entityColorMap.get(name);
886
+ if (!color) {
887
+ const palette = this.getActivePalette();
888
+ color = palette[this.entityColorMap.size % palette.length];
889
+ this.entityColorMap.set(name, color);
890
+ }
891
+ return color;
892
+ }
893
+ /** Distinct source entities present in the data (for the entity legend). */
894
+ get EntityLegend() {
895
+ if (this.ColorBy !== 'entity')
896
+ return [];
897
+ const counts = new Map();
898
+ for (const p of this.Points) {
899
+ const name = p.Metadata?.['EntityName'] || p.Metadata?.['Entity'] || 'Unknown';
900
+ counts.set(name, (counts.get(name) ?? 0) + 1);
901
+ }
902
+ return [...counts.entries()].map(([Name, Count]) => ({
903
+ Name,
904
+ Count,
905
+ Color: this.getEntityColor({ Metadata: { EntityName: Name } }),
906
+ }));
907
+ }
908
+ // ================================================================
909
+ // 3D projection (static isometric, depth-cued)
910
+ // ================================================================
911
+ /** True when any point carries a Z coordinate (a 3D projection). */
912
+ get Is3D() {
913
+ return this.Points.some(p => p.Z != null);
914
+ }
915
+ /** Rotate a centered (x,y,z) by the current yaw (around Y) then pitch (around X). */
916
+ rotate(point) {
917
+ const x0 = point.X - 500;
918
+ const y0 = point.Y - 350;
919
+ const z0 = (point.Z ?? 500) - 500;
920
+ const cy = Math.cos(this.Yaw), sy = Math.sin(this.Yaw);
921
+ // yaw around vertical (Y) axis
922
+ const x1 = x0 * cy + z0 * sy;
923
+ const z1 = -x0 * sy + z0 * cy;
924
+ const y1 = y0;
925
+ // pitch around horizontal (X) axis
926
+ const cp = Math.cos(this.Pitch), sp = Math.sin(this.Pitch);
927
+ const y2 = y1 * cp - z1 * sp;
928
+ const z2 = y1 * sp + z1 * cp;
929
+ return { x: x1, y: y2, z: z2 };
930
+ }
931
+ /** Project a point's (X,Y,Z) to a screen X in the SVG coordinate space. */
932
+ PX(point) {
933
+ if (!this.Is3D || point.Z == null)
934
+ return point.X;
935
+ return 500 + this.rotate(point).x;
936
+ }
937
+ /** Project a point's (X,Y,Z) to a screen Y in the SVG coordinate space. */
938
+ PY(point) {
939
+ if (!this.Is3D || point.Z == null)
940
+ return point.Y;
941
+ return 350 + this.rotate(point).y;
942
+ }
943
+ /** Normalized depth [0=far, 1=near] for a point, used for size/opacity cues. */
944
+ depthNorm(point) {
945
+ if (!this.Is3D || point.Z == null)
946
+ return 1;
947
+ const depth = this.rotate(point).z;
948
+ // centered coords span roughly ±440 per axis; combined depth ~[-760, 760]
949
+ return Math.max(0, Math.min(1, (depth + 600) / 1200));
950
+ }
951
+ /** Reset the 3D orbit to the default viewing angle. */
952
+ ResetRotation() {
953
+ this.Yaw = 0.6;
954
+ this.Pitch = 0.45;
955
+ this.cdr.detectChanges();
956
+ }
957
+ /**
958
+ * Cluster labels positioned at each cluster's centroid in *projected* space.
959
+ * Used to label clusters in 3D (where the 2D centroid-glow path is disabled).
960
+ * Recomputes as the user orbits because it depends on PX/PY (yaw/pitch).
961
+ */
962
+ get ProjectedClusterLabels() {
963
+ if (!this.Is3D)
964
+ return [];
965
+ const groups = new Map();
966
+ for (const p of this.Points) {
967
+ if (p.ClusterId < 0)
968
+ continue;
969
+ const arr = groups.get(p.ClusterId);
970
+ if (arr)
971
+ arr.push(p);
972
+ else
973
+ groups.set(p.ClusterId, [p]);
974
+ }
975
+ const palette = this.getActivePalette();
976
+ const out = [];
977
+ for (const [cid, pts] of groups) {
978
+ const sx = pts.reduce((s, p) => s + this.PX(p), 0) / pts.length;
979
+ const sy = pts.reduce((s, p) => s + this.PY(p), 0) / pts.length;
980
+ const cluster = this.Clusters.find(c => c.Id === cid);
981
+ out.push({ X: sx, Y: sy, ClusterId: cid, Color: cluster?.Color ?? palette[cid % palette.length] });
982
+ }
983
+ return out;
984
+ }
985
+ /**
986
+ * Points in render order. In 3D, far points are drawn first so nearer points
987
+ * paint on top (SVG has no z-index); in 2D the original order is preserved.
988
+ */
989
+ get RenderPoints() {
990
+ if (!this.Is3D)
991
+ return this.Points;
992
+ return [...this.Points].sort((a, b) => this.depthNorm(a) - this.depthNorm(b));
993
+ }
782
994
  /**
783
995
  * Compute the SVG radius for a data point.
784
996
  *
@@ -791,6 +1003,10 @@ export class ClusterScatterComponent {
791
1003
  if (this.SelectedPointIds.has(point.VectorKey)) {
792
1004
  return this.HighlightRadius;
793
1005
  }
1006
+ if (this.Is3D) {
1007
+ // Nearer points (depth→1) render larger to convey depth.
1008
+ return this.DotRadius * (0.6 + 0.7 * this.depthNorm(point));
1009
+ }
794
1010
  return this.DotRadius;
795
1011
  }
796
1012
  /**
@@ -805,6 +1021,10 @@ export class ClusterScatterComponent {
805
1021
  if (this.HoveredPoint && this.HoveredPoint.VectorKey !== point.VectorKey) {
806
1022
  return this.DotOpacity * 0.5;
807
1023
  }
1024
+ if (this.Is3D) {
1025
+ // Fade far points slightly for depth perception.
1026
+ return this.DotOpacity * (0.55 + 0.45 * this.depthNorm(point));
1027
+ }
808
1028
  return this.DotOpacity;
809
1029
  }
810
1030
  /**
@@ -922,7 +1142,19 @@ export class ClusterScatterComponent {
922
1142
  * @param event The native mouse event.
923
1143
  */
924
1144
  OnMouseDown(event) {
925
- if (event.button !== 0 || !this.EnablePan)
1145
+ if (event.button !== 0)
1146
+ return;
1147
+ // In 3D mode a left-drag orbits the camera (so depth is perceivable);
1148
+ // in 2D it pans the viewBox.
1149
+ if (this.Is3D) {
1150
+ this.isRotating = true;
1151
+ this.rotateStartX = event.clientX;
1152
+ this.rotateStartY = event.clientY;
1153
+ this.rotateStartYaw = this.Yaw;
1154
+ this.rotateStartPitch = this.Pitch;
1155
+ return;
1156
+ }
1157
+ if (!this.EnablePan)
926
1158
  return;
927
1159
  this.isPanning = true;
928
1160
  this.panStartX = event.clientX;
@@ -1209,6 +1441,15 @@ export class ClusterScatterComponent {
1209
1441
  // Private Methods
1210
1442
  // ================================================================
1211
1443
  onMouseMove(event) {
1444
+ if (this.isRotating) {
1445
+ const dx = event.clientX - this.rotateStartX;
1446
+ const dy = event.clientY - this.rotateStartY;
1447
+ this.Yaw = this.rotateStartYaw + dx * 0.01;
1448
+ // clamp pitch so the scene never flips upside down
1449
+ this.Pitch = Math.max(-1.3, Math.min(1.3, this.rotateStartPitch + dy * 0.01));
1450
+ this.cdr.detectChanges();
1451
+ return;
1452
+ }
1212
1453
  if (!this.isPanning || !this.svgRef)
1213
1454
  return;
1214
1455
  const svg = this.svgRef.nativeElement;
@@ -1229,6 +1470,7 @@ export class ClusterScatterComponent {
1229
1470
  }
1230
1471
  onMouseUp(_event) {
1231
1472
  this.isPanning = false;
1473
+ this.isRotating = false;
1232
1474
  }
1233
1475
  computeCentroids() {
1234
1476
  if (!this.Points.length || !this.Clusters.length) {
@@ -1312,15 +1554,17 @@ export class ClusterScatterComponent {
1312
1554
  } if (rf & 2) {
1313
1555
  let _t;
1314
1556
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.svgRef = _t.first);
1315
- } }, inputs: { Points: "Points", Clusters: "Clusters", IsLoading: "IsLoading", DotRadius: "DotRadius", DotOpacity: "DotOpacity", HighlightRadius: "HighlightRadius", ShowLegend: "ShowLegend", ShowTooltip: "ShowTooltip", TooltipFields: "TooltipFields", FieldPriority: "FieldPriority", FieldDisplayNames: "FieldDisplayNames", EntityName: "EntityName", ColorPalette: "ColorPalette", EnableZoom: "EnableZoom", EnablePan: "EnablePan", AnimateTransitions: "AnimateTransitions", SelectedPointIds: "SelectedPointIds", MinZoom: "MinZoom", MaxZoom: "MaxZoom" }, outputs: { PointClicked: "PointClicked", OpenRecordRequested: "OpenRecordRequested", PointHovered: "PointHovered", BeforePointClick: "BeforePointClick", BeforePointHover: "BeforePointHover", BeforeZoom: "BeforeZoom", AfterClusteringComplete: "AfterClusteringComplete", ClusterSelected: "ClusterSelected", SelectionChanged: "SelectionChanged", ViewportChanged: "ViewportChanged", LabelEdited: "LabelEdited" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 8, vars: 8, consts: [["svgElement", ""], [1, "scatter-container"], [1, "loading-overlay"], [1, "empty-state"], ["preserveAspectRatio", "xMidYMid meet", 1, "scatter-svg", 3, "no-transitions"], [1, "legend"], [1, "tooltip", 3, "left", "top"], [1, "detail-panel"], [1, "loading-spinner"], [1, "fa-solid", "fa-circle-nodes", "fa-spin"], [1, "fa-solid", "fa-chart-scatter"], [1, "empty-hint"], ["preserveAspectRatio", "xMidYMid meet", 1, "scatter-svg", 3, "wheel", "mousedown"], ["cx", "50%", "cy", "50%", "r", "50%"], [1, "grid-lines"], [1, "data-point"], ["offset", "0%", "stop-opacity", "0.12"], ["offset", "100%", "stop-opacity", "0"], ["text-anchor", "middle", 1, "cluster-label"], [1, "data-point", 3, "mouseenter", "mouseleave", "click"], ["fill", "none", "stroke-width", "2", "opacity", "0.8", 1, "selection-ring"], ["fill", "none", "stroke-width", "2.5", "opacity", "0.9"], [1, "legend-title"], [1, "legend-item"], [1, "legend-item", 3, "click"], [1, "legend-dot"], ["autofocus", "", 1, "legend-label-input", 3, "ngModel"], [1, "legend-count"], ["autofocus", "", 1, "legend-label-input", 3, "ngModelChange", "keydown", "blur", "click", "ngModel"], [1, "legend-label"], ["title", "Edit label", 1, "legend-edit-btn", 3, "click"], [1, "fa-solid", "fa-pencil"], [1, "tooltip"], ["Variant", "compact", 3, "EntityName", "Record"], [1, "tt-cluster"], [1, "fa-solid", "fa-circle", 2, "font-size", "7px", "vertical-align", "middle"], [1, "tt-action"], [1, "fa-solid", "fa-arrow-up-right-from-square"], [1, "detail-header"], [1, "detail-header-content"], [1, "detail-icon"], [1, "detail-header-text"], [1, "detail-name"], [1, "detail-entity-badge"], ["title", "Close", 1, "detail-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "detail-cluster"], [1, "detail-cluster-dot"], ["Variant", "detail", "CssClass", "detail-entity-card", 3, "EntityName", "Record"], [1, "detail-members-section"], [1, "detail-members-toggle", 3, "click"], [1, "fa-solid"], [1, "detail-members-count"], [1, "detail-members-list"], [1, "detail-footer"], [1, "detail-open-btn", 3, "click"], [1, "detail-member-row", 3, "active"], [1, "detail-member-row", 3, "click"], [1, "member-icon", 3, "class"], [1, "member-label"], [1, "member-icon"]], template: function ClusterScatterComponent_Template(rf, ctx) { if (rf & 1) {
1557
+ } }, inputs: { Points: "Points", Clusters: "Clusters", ColorBy: "ColorBy", IsLoading: "IsLoading", DotRadius: "DotRadius", DotOpacity: "DotOpacity", HighlightRadius: "HighlightRadius", ShowLegend: "ShowLegend", ShowTooltip: "ShowTooltip", TooltipFields: "TooltipFields", FieldPriority: "FieldPriority", FieldDisplayNames: "FieldDisplayNames", EntityName: "EntityName", ColorPalette: "ColorPalette", EnableZoom: "EnableZoom", EnablePan: "EnablePan", AnimateTransitions: "AnimateTransitions", SelectedPointIds: "SelectedPointIds", MinZoom: "MinZoom", MaxZoom: "MaxZoom" }, outputs: { PointClicked: "PointClicked", OpenRecordRequested: "OpenRecordRequested", PointHovered: "PointHovered", BeforePointClick: "BeforePointClick", BeforePointHover: "BeforePointHover", BeforeZoom: "BeforeZoom", AfterClusteringComplete: "AfterClusteringComplete", ClusterSelected: "ClusterSelected", SelectionChanged: "SelectionChanged", ViewportChanged: "ViewportChanged", LabelEdited: "LabelEdited" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], decls: 10, vars: 10, consts: [["svgElement", ""], [1, "scatter-container"], [1, "loading-overlay"], [1, "empty-state"], ["preserveAspectRatio", "xMidYMid meet", 1, "scatter-svg", 3, "no-transitions"], [1, "threed-hint"], [1, "legend"], [1, "tooltip", 3, "left", "top"], [1, "detail-panel"], [1, "loading-spinner"], [1, "fa-solid", "fa-circle-nodes", "fa-spin"], [1, "fa-solid", "fa-chart-scatter"], [1, "empty-hint"], ["preserveAspectRatio", "xMidYMid meet", 1, "scatter-svg", 3, "wheel", "mousedown"], ["cx", "50%", "cy", "50%", "r", "50%"], [1, "grid-lines"], [1, "data-point"], ["offset", "0%", "stop-opacity", "0.12"], ["offset", "100%", "stop-opacity", "0"], ["text-anchor", "middle", 1, "cluster-label"], [1, "data-point", 3, "mouseenter", "mouseleave", "click"], ["text-anchor", "middle", 1, "cluster-label", "cluster-label-3d"], ["fill", "none", "stroke-width", "2", "opacity", "0.8", 1, "selection-ring"], ["fill", "none", "stroke-width", "2.5", "opacity", "0.9"], [1, "fa-solid", "fa-cube"], ["title", "Reset view", 1, "threed-reset", 3, "click"], [1, "fa-solid", "fa-rotate-left"], [1, "legend-title"], [1, "legend-item"], [1, "legend-dot"], [1, "legend-label"], [1, "legend-count"], [1, "legend-item", 3, "click"], ["autofocus", "", 1, "legend-label-input", 3, "ngModel"], ["autofocus", "", 1, "legend-label-input", 3, "ngModelChange", "keydown", "blur", "click", "ngModel"], ["title", "Edit label", 1, "legend-edit-btn", 3, "click"], [1, "fa-solid", "fa-pencil"], [1, "tooltip"], ["Variant", "compact", 3, "EntityName", "Record"], [1, "tt-cluster"], [1, "fa-solid", "fa-circle", 2, "font-size", "7px", "vertical-align", "middle"], [1, "tt-action"], [1, "fa-solid", "fa-arrow-up-right-from-square"], [1, "detail-header"], [1, "detail-header-content"], [1, "detail-icon"], [1, "detail-header-text"], [1, "detail-name"], [1, "detail-entity-badge"], ["title", "Close", 1, "detail-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "detail-cluster"], [1, "detail-cluster-dot"], ["Variant", "detail", "CssClass", "detail-entity-card", 3, "EntityName", "Record"], [1, "detail-members-section"], [1, "detail-members-toggle", 3, "click"], [1, "fa-solid"], [1, "detail-members-count"], [1, "detail-members-list"], [1, "detail-footer"], [1, "detail-open-btn", 3, "click"], [1, "detail-member-row", 3, "active"], [1, "detail-member-row", 3, "click"], [1, "member-icon", 3, "class"], [1, "member-label"], [1, "member-icon"]], template: function ClusterScatterComponent_Template(rf, ctx) { if (rf & 1) {
1316
1558
  i0.ɵɵelementStart(0, "div", 1);
1317
1559
  i0.ɵɵconditionalCreate(1, ClusterScatterComponent_Conditional_1_Template, 5, 0, "div", 2);
1318
1560
  i0.ɵɵconditionalCreate(2, ClusterScatterComponent_Conditional_2_Template, 6, 0, "div", 3);
1319
- i0.ɵɵconditionalCreate(3, ClusterScatterComponent_Conditional_3_Template, 19, 4, ":svg:svg", 4);
1320
- i0.ɵɵconditionalCreate(4, ClusterScatterComponent_Conditional_4_Template, 5, 0, "div", 5);
1321
- i0.ɵɵconditionalCreate(5, ClusterScatterComponent_Conditional_5_Template, 8, 7, "div", 6);
1322
- i0.ɵɵelementStart(6, "div", 7);
1323
- i0.ɵɵconditionalCreate(7, ClusterScatterComponent_Conditional_7_Template, 25, 14);
1561
+ i0.ɵɵconditionalCreate(3, ClusterScatterComponent_Conditional_3_Template, 17, 6, ":svg:svg", 4);
1562
+ i0.ɵɵconditionalCreate(4, ClusterScatterComponent_Conditional_4_Template, 6, 0, "div", 5);
1563
+ i0.ɵɵconditionalCreate(5, ClusterScatterComponent_Conditional_5_Template, 5, 0, "div", 6);
1564
+ i0.ɵɵconditionalCreate(6, ClusterScatterComponent_Conditional_6_Template, 5, 0, "div", 6);
1565
+ i0.ɵɵconditionalCreate(7, ClusterScatterComponent_Conditional_7_Template, 8, 7, "div", 7);
1566
+ i0.ɵɵelementStart(8, "div", 8);
1567
+ i0.ɵɵconditionalCreate(9, ClusterScatterComponent_Conditional_9_Template, 25, 14);
1324
1568
  i0.ɵɵelementEnd()();
1325
1569
  } if (rf & 2) {
1326
1570
  i0.ɵɵadvance();
@@ -1330,22 +1574,28 @@ export class ClusterScatterComponent {
1330
1574
  i0.ɵɵadvance();
1331
1575
  i0.ɵɵconditional(ctx.Points.length > 0 ? 3 : -1);
1332
1576
  i0.ɵɵadvance();
1333
- i0.ɵɵconditional(ctx.ShowLegend && ctx.Clusters.length > 0 ? 4 : -1);
1577
+ i0.ɵɵconditional(ctx.Points.length > 0 && ctx.Is3D ? 4 : -1);
1334
1578
  i0.ɵɵadvance();
1335
- i0.ɵɵconditional(ctx.ShowTooltip && ctx.HoveredPoint ? 5 : -1);
1579
+ i0.ɵɵconditional(ctx.ShowLegend && ctx.ColorBy === "entity" && ctx.EntityLegend.length > 0 ? 5 : -1);
1580
+ i0.ɵɵadvance();
1581
+ i0.ɵɵconditional(ctx.ShowLegend && ctx.ColorBy !== "entity" && ctx.Clusters.length > 0 ? 6 : -1);
1582
+ i0.ɵɵadvance();
1583
+ i0.ɵɵconditional(ctx.ShowTooltip && ctx.HoveredPoint ? 7 : -1);
1336
1584
  i0.ɵɵadvance();
1337
1585
  i0.ɵɵclassProp("detail-panel-open", ctx.ShowDetailPanel && ctx.SelectedPoint);
1338
1586
  i0.ɵɵadvance();
1339
- i0.ɵɵconditional(ctx.SelectedPoint ? 7 : -1);
1340
- } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.MJEntityCardComponent], styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.scatter-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n/* SVG */\n.scatter-svg {\n width: 100%;\n height: 100%;\n cursor: grab;\n user-select: none;\n}\n\n.scatter-svg:active {\n cursor: grabbing;\n}\n\n/* Grid lines */\n.grid-lines line {\n stroke: var(--mj-border-subtle);\n stroke-width: 0.5;\n}\n\n/* Data points */\n.data-point {\n cursor: pointer;\n transition: opacity 0.15s ease, r 0.15s ease;\n}\n\n.data-point:hover {\n filter: brightness(1.2);\n}\n\n/* Selection rings */\n.selection-ring {\n pointer-events: none;\n transition: r 0.15s ease;\n}\n\n/* Disable transitions when AnimateTransitions is false */\n.no-transitions .data-point,\n.no-transitions .selection-ring {\n transition: none;\n}\n\n/* Loading overlay */\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-page) 80%, transparent);\n z-index: 5;\n}\n\n.loading-spinner {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n}\n\n.loading-spinner i {\n font-size: 2rem;\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.empty-state {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n gap: 8px;\n}\n\n.empty-state i {\n font-size: 3rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.empty-state p {\n font-size: 1rem;\n margin: 0;\n}\n\n.empty-hint {\n font-size: 0.8rem !important;\n opacity: 0.7;\n}\n\n/* Legend */\n.legend {\n position: absolute;\n bottom: 20px;\n left: 20px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(12px);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 18px;\n z-index: 3;\n}\n\n.legend-title {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 0.78rem;\n margin-bottom: 4px;\n color: var(--mj-text-primary);\n cursor: pointer;\n border-radius: 4px;\n padding: 2px 4px;\n margin-left: -4px;\n margin-right: -4px;\n transition: background 0.15s ease;\n}\n\n.legend-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.legend-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.legend-label {\n flex: 1;\n}\n\n.legend-edit-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6rem;\n border-radius: 4px;\n transition: color 0.15s ease;\n}\n\n.legend-item:hover .legend-edit-btn {\n display: inline-flex;\n}\n\n.legend-edit-btn:hover {\n color: var(--mj-brand-primary);\n}\n\n.legend-label-input {\n flex: 1;\n padding: 2px 4px;\n border: 1px solid var(--mj-border-focus);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.75rem;\n outline: none;\n min-width: 60px;\n}\n\n.legend-count {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n margin-left: auto;\n padding-left: 16px;\n}\n\n/* Cluster labels on chart */\n.cluster-label {\n font-size: 14px;\n font-weight: 600;\n opacity: 0.85;\n pointer-events: none;\n text-shadow:\n 0 0 6px var(--mj-bg-page),\n 0 0 12px var(--mj-bg-page),\n 1px 1px 2px var(--mj-bg-page);\n}\n\n/* Tooltip */\n.tooltip {\n position: fixed;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 96%, transparent);\n backdrop-filter: blur(16px);\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n padding: 14px 16px;\n width: 280px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n pointer-events: none;\n z-index: 50;\n}\n\n.tt-header {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n margin-bottom: 6px;\n}\n\n.tt-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.tt-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.tt-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n margin-top: 1px;\n}\n\n.tt-cluster {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.tt-fields {\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.tt-field-row {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-action {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n.tt-action i {\n margin-right: 4px;\n}\n\n/* ================================================================\n Detail Panel (right slide-in)\n ================================================================ */\n\n.detail-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 320px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(16px);\n border-left: 1px solid var(--mj-border-default);\n z-index: 10;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: -4px 0 24px color-mix(in srgb, var(--mj-bg-page) 30%, transparent);\n overflow: hidden;\n}\n\n.detail-panel-open {\n transform: translateX(0);\n}\n\n/* Header */\n.detail-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 16px 16px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-header-content {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.detail-icon {\n width: 2.25rem;\n height: 2.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.95rem;\n flex-shrink: 0;\n}\n\n.detail-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.detail-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n word-wrap: break-word;\n}\n\n.detail-entity-badge {\n display: inline-block;\n margin-top: 4px;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 0.68rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.detail-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n padding: 4px 6px;\n border-radius: 4px;\n transition: color 0.15s ease, background 0.15s ease;\n flex-shrink: 0;\n}\n\n.detail-close:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Cluster badge */\n.detail-cluster {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.detail-cluster-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n/* Metadata fields */\n.detail-fields {\n padding: 4px 16px 8px;\n overflow-y: auto;\n flex-shrink: 0;\n}\n\n.detail-field {\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-field:last-child {\n border-bottom: none;\n}\n\n.detail-field-key {\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n\n.detail-field-value {\n font-size: 0.8rem;\n color: var(--mj-text-primary);\n line-height: 1.4;\n word-wrap: break-word;\n}\n\n/* Cluster Members Section */\n.detail-members-section {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.detail-members-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: left;\n transition: background 0.15s ease;\n}\n\n.detail-members-toggle:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-members-toggle i {\n font-size: 0.65rem;\n width: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.detail-members-count {\n margin-left: auto;\n font-size: 0.7rem;\n font-weight: 400;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 1px 7px;\n border-radius: 8px;\n}\n\n.detail-members-list {\n flex: 1;\n overflow-y: auto;\n padding: 0 8px 8px;\n}\n\n.detail-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.12s ease;\n}\n\n.detail-member-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-member-row.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.detail-member-row .member-icon {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.detail-member-row.active .member-icon {\n color: var(--mj-brand-primary);\n}\n\n.member-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Footer */\n.detail-footer {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.detail-open-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.detail-open-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.detail-open-btn i {\n font-size: 0.75rem;\n}\n"], encapsulation: 2 }); }
1587
+ i0.ɵɵconditional(ctx.SelectedPoint ? 9 : -1);
1588
+ } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.MJEntityCardComponent], styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.scatter-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n/* SVG */\n.scatter-svg {\n width: 100%;\n height: 100%;\n cursor: grab;\n user-select: none;\n}\n\n.scatter-svg:active {\n cursor: grabbing;\n}\n\n/* Grid lines */\n.grid-lines line {\n stroke: var(--mj-border-subtle);\n stroke-width: 0.5;\n}\n\n/* Data points */\n.data-point {\n cursor: pointer;\n transition: opacity 0.15s ease, r 0.15s ease;\n}\n\n.data-point:hover {\n filter: brightness(1.2);\n}\n\n/* Selection rings */\n.selection-ring {\n pointer-events: none;\n transition: r 0.15s ease;\n}\n\n/* Disable transitions when AnimateTransitions is false */\n.no-transitions .data-point,\n.no-transitions .selection-ring {\n transition: none;\n}\n\n/* Loading overlay */\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-page) 80%, transparent);\n z-index: 5;\n}\n\n.loading-spinner {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n}\n\n.loading-spinner i {\n font-size: 2rem;\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.empty-state {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n gap: 8px;\n}\n\n.empty-state i {\n font-size: 3rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.empty-state p {\n font-size: 1rem;\n margin: 0;\n}\n\n.empty-hint {\n font-size: 0.8rem !important;\n opacity: 0.7;\n}\n\n/* Legend */\n.legend {\n position: absolute;\n bottom: 20px;\n left: 20px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(12px);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 18px;\n z-index: 3;\n}\n\n.legend-title {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 0.78rem;\n margin-bottom: 4px;\n color: var(--mj-text-primary);\n cursor: pointer;\n border-radius: 4px;\n padding: 2px 4px;\n margin-left: -4px;\n margin-right: -4px;\n transition: background 0.15s ease;\n}\n\n.legend-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.legend-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.legend-label {\n flex: 1;\n}\n\n.legend-edit-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6rem;\n border-radius: 4px;\n transition: color 0.15s ease;\n}\n\n.legend-item:hover .legend-edit-btn {\n display: inline-flex;\n}\n\n.legend-edit-btn:hover {\n color: var(--mj-brand-primary);\n}\n\n.legend-label-input {\n flex: 1;\n padding: 2px 4px;\n border: 1px solid var(--mj-border-focus);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.75rem;\n outline: none;\n min-width: 60px;\n}\n\n.legend-count {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n margin-left: auto;\n padding-left: 16px;\n}\n\n/* Cluster labels on chart */\n.cluster-label {\n font-size: 14px;\n font-weight: 600;\n opacity: 0.85;\n pointer-events: none;\n text-shadow:\n 0 0 6px var(--mj-bg-page),\n 0 0 12px var(--mj-bg-page),\n 1px 1px 2px var(--mj-bg-page);\n}\n\n/* Tooltip */\n.tooltip {\n position: fixed;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 96%, transparent);\n backdrop-filter: blur(16px);\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n padding: 14px 16px;\n width: 280px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n pointer-events: none;\n z-index: 50;\n}\n\n.tt-header {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n margin-bottom: 6px;\n}\n\n.tt-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.tt-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.tt-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n margin-top: 1px;\n}\n\n.tt-cluster {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.tt-fields {\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.tt-field-row {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-action {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n.tt-action i {\n margin-right: 4px;\n}\n\n/* ================================================================\n Detail Panel (right slide-in)\n ================================================================ */\n\n.detail-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 320px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(16px);\n border-left: 1px solid var(--mj-border-default);\n z-index: 10;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: -4px 0 24px color-mix(in srgb, var(--mj-bg-page) 30%, transparent);\n overflow: hidden;\n}\n\n.detail-panel-open {\n transform: translateX(0);\n}\n\n/* Header */\n.detail-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 16px 16px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-header-content {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.detail-icon {\n width: 2.25rem;\n height: 2.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.95rem;\n flex-shrink: 0;\n}\n\n.detail-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.detail-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n word-wrap: break-word;\n}\n\n.detail-entity-badge {\n display: inline-block;\n margin-top: 4px;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 0.68rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.detail-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n padding: 4px 6px;\n border-radius: 4px;\n transition: color 0.15s ease, background 0.15s ease;\n flex-shrink: 0;\n}\n\n.detail-close:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Cluster badge */\n.detail-cluster {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.detail-cluster-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n/* Metadata fields */\n.detail-fields {\n padding: 4px 16px 8px;\n overflow-y: auto;\n flex-shrink: 0;\n}\n\n.detail-field {\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-field:last-child {\n border-bottom: none;\n}\n\n.detail-field-key {\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n\n.detail-field-value {\n font-size: 0.8rem;\n color: var(--mj-text-primary);\n line-height: 1.4;\n word-wrap: break-word;\n}\n\n/* Cluster Members Section */\n.detail-members-section {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.detail-members-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: left;\n transition: background 0.15s ease;\n}\n\n.detail-members-toggle:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-members-toggle i {\n font-size: 0.65rem;\n width: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.detail-members-count {\n margin-left: auto;\n font-size: 0.7rem;\n font-weight: 400;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 1px 7px;\n border-radius: 8px;\n}\n\n.detail-members-list {\n flex: 1;\n overflow-y: auto;\n padding: 0 8px 8px;\n}\n\n.detail-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.12s ease;\n}\n\n.detail-member-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-member-row.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.detail-member-row .member-icon {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.detail-member-row.active .member-icon {\n color: var(--mj-brand-primary);\n}\n\n.member-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Footer */\n.detail-footer {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.detail-open-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.detail-open-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.detail-open-btn i {\n font-size: 0.75rem;\n}\n\n/* 3D orbit hint + reset */\n.threed-hint {\n position: absolute;\n top: 12px;\n right: 12px;\n z-index: 6;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 8px 5px 10px;\n background: color-mix(in srgb, var(--mj-bg-surface) 88%, transparent);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n backdrop-filter: blur(4px);\n}\n.threed-hint > i { color: var(--mj-brand-primary); }\n.threed-reset {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 6px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n.threed-reset:hover { color: var(--mj-brand-primary); border-color: var(--mj-brand-primary); }\n\n/* 3D cluster labels \u2014 halo for readability over points */\n.cluster-label-3d {\n font-weight: 700;\n font-size: 13px;\n paint-order: stroke;\n stroke: var(--mj-bg-surface);\n stroke-width: 3px;\n stroke-linejoin: round;\n pointer-events: none;\n}\n"], encapsulation: 2 }); }
1341
1589
  }
1342
1590
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ClusterScatterComponent, [{
1343
1591
  type: Component,
1344
- args: [{ standalone: false, selector: 'mj-cluster-scatter', encapsulation: ViewEncapsulation.None, template: "<div class=\"scatter-container\">\n <!-- Loading overlay -->\n @if (IsLoading) {\n <div class=\"loading-overlay\">\n <div class=\"loading-spinner\">\n <i class=\"fa-solid fa-circle-nodes fa-spin\"></i>\n <span>Computing clusters...</span>\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!IsLoading && Points.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-chart-scatter\"></i>\n <p>No data to display</p>\n <p class=\"empty-hint\">Configure and run clustering to see results</p>\n </div>\n }\n\n <!-- SVG scatter plot -->\n @if (Points.length > 0) {\n <svg #svgElement\n class=\"scatter-svg\"\n [class.no-transitions]=\"!AnimateTransitions\"\n [attr.viewBox]=\"ViewBoxString\"\n preserveAspectRatio=\"xMidYMid meet\"\n (wheel)=\"OnWheel($event)\"\n (mousedown)=\"OnMouseDown($event)\">\n\n <!-- Gradient definitions for cluster glow -->\n <defs>\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <radialGradient [attr.id]=\"'cluster-glow-' + centroid.ClusterId\"\n cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0.12\"/>\n <stop offset=\"100%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0\"/>\n </radialGradient>\n }\n </defs>\n\n <!-- Background grid lines -->\n <g class=\"grid-lines\">\n @for (y of HorizontalGridLines; track y) {\n <line [attr.x1]=\"0\" [attr.y1]=\"y\" [attr.x2]=\"1000\" [attr.y2]=\"y\"/>\n }\n @for (x of VerticalGridLines; track x) {\n <line [attr.x1]=\"x\" [attr.y1]=\"0\" [attr.x2]=\"x\" [attr.y2]=\"700\"/>\n }\n </g>\n\n <!-- Cluster glow circles -->\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <circle [attr.cx]=\"centroid.Cx\"\n [attr.cy]=\"centroid.Cy\"\n [attr.r]=\"centroid.R\"\n [attr.fill]=\"'url(#cluster-glow-' + centroid.ClusterId + ')'\"/>\n }\n\n <!-- Cluster labels (positioned above centroids) -->\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n @if (GetClusterLabelById(centroid.ClusterId); as label) {\n <text class=\"cluster-label\"\n [attr.x]=\"centroid.Cx\"\n [attr.y]=\"centroid.Cy - centroid.R - 8\"\n text-anchor=\"middle\"\n [attr.fill]=\"centroid.Color\">{{ label }}</text>\n }\n }\n\n <!-- Data points -->\n @for (point of Points; track TrackPointBy($index, point)) {\n <circle class=\"data-point\"\n [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"GetPointRadius(point)\"\n [attr.fill]=\"GetPointColor(point)\"\n [attr.opacity]=\"GetPointOpacity(point)\"\n (mouseenter)=\"OnPointMouseEnter(point, $event)\"\n (mouseleave)=\"OnPointMouseLeave()\"\n (click)=\"OnPointClick(point); $event.stopPropagation()\"/>\n }\n\n <!-- Selection rings for selected points -->\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsSelected(point)) {\n <circle class=\"selection-ring\"\n [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"HighlightRadius + 2\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2\"\n opacity=\"0.8\"/>\n }\n }\n\n <!-- Highlight ring for last-clicked point -->\n @if (HighlightedKey) {\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsHighlighted(point)) {\n <circle [attr.cx]=\"point.X\"\n [attr.cy]=\"point.Y\"\n [attr.r]=\"HighlightRadius + 4\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2.5\"\n opacity=\"0.9\"/>\n }\n }\n }\n </svg>\n }\n\n <!-- Legend overlay -->\n @if (ShowLegend && Clusters.length > 0) {\n <div class=\"legend\">\n <div class=\"legend-title\">Clusters</div>\n @for (cluster of Clusters; track TrackClusterBy($index, cluster)) {\n <div class=\"legend-item\" (click)=\"OnClusterLegendClick(cluster)\">\n <div class=\"legend-dot\" [style.background]=\"cluster.Color\"></div>\n @if (EditingClusterId === cluster.Id) {\n <input class=\"legend-label-input\"\n [(ngModel)]=\"EditingLabelDraft\"\n (keydown)=\"OnLabelEditKeydown($event, cluster)\"\n (blur)=\"CommitLabelEdit(cluster)\"\n (click)=\"$event.stopPropagation()\"\n autofocus />\n } @else {\n <span class=\"legend-label\">{{ cluster.Label }}</span>\n <button class=\"legend-edit-btn\"\n title=\"Edit label\"\n (click)=\"StartLabelEdit(cluster, $event)\">\n <i class=\"fa-solid fa-pencil\"></i>\n </button>\n }\n <span class=\"legend-count\">{{ cluster.MemberCount }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Tooltip -->\n @if (ShowTooltip && HoveredPoint) {\n <div class=\"tooltip\"\n [style.left.px]=\"TooltipX\"\n [style.top.px]=\"TooltipY\">\n <mj-entity-card\n [EntityName]=\"EntityName\"\n [Record]=\"HoveredPoint.Metadata\"\n Variant=\"compact\">\n </mj-entity-card>\n <div class=\"tt-cluster\">\n <i class=\"fa-solid fa-circle\" style=\"font-size: 7px; vertical-align: middle\"></i>\n {{ GetClusterLabel(HoveredPoint) }}\n </div>\n <div class=\"tt-action\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Click to view details\n </div>\n </div>\n }\n\n <!-- Detail Panel (slide-in from right) -->\n <div class=\"detail-panel\" [class.detail-panel-open]=\"ShowDetailPanel && SelectedPoint\">\n @if (SelectedPoint) {\n <!-- Panel Header -->\n <div class=\"detail-header\">\n <div class=\"detail-header-content\">\n @if (SelectedPoint.Metadata?.['EntityIcon']) {\n <div class=\"detail-icon\">\n <i [class]=\"SelectedPoint.Metadata['EntityIcon']\"></i>\n </div>\n }\n <div class=\"detail-header-text\">\n <div class=\"detail-name\">{{ SelectedPoint.Label }}</div>\n @if (SelectedPoint.Metadata?.['Entity']) {\n <span class=\"detail-entity-badge\">{{ SelectedPoint.Metadata['Entity'] }}</span>\n }\n </div>\n </div>\n <button class=\"detail-close\" (click)=\"CloseDetailPanel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Cluster badge -->\n <div class=\"detail-cluster\">\n <span class=\"detail-cluster-dot\"\n [style.background]=\"GetClusterColor(SelectedPoint.ClusterId)\"></span>\n {{ GetClusterLabel(SelectedPoint) }}\n </div>\n\n <!-- Metadata fields via entity card -->\n <mj-entity-card\n [EntityName]=\"EntityName\"\n [Record]=\"SelectedPoint.Metadata\"\n Variant=\"detail\"\n CssClass=\"detail-entity-card\">\n </mj-entity-card>\n\n <!-- Cluster Members -->\n <div class=\"detail-members-section\">\n <button class=\"detail-members-toggle\" (click)=\"ToggleClusterMembers()\">\n <i class=\"fa-solid\"\n [class.fa-chevron-down]=\"ClusterMembersExpanded\"\n [class.fa-chevron-right]=\"!ClusterMembersExpanded\"></i>\n <span>Cluster Members</span>\n <span class=\"detail-members-count\">{{ ClusterMembers.length }}</span>\n </button>\n\n @if (ClusterMembersExpanded) {\n <div class=\"detail-members-list\">\n @for (member of ClusterMembers; track TrackMemberBy($index, member)) {\n <div class=\"detail-member-row\"\n [class.active]=\"member.VectorKey === SelectedPoint.VectorKey\"\n (click)=\"OnClusterMemberClick(member)\">\n @if (member.Metadata?.['EntityIcon']) {\n <i [class]=\"member.Metadata['EntityIcon']\" class=\"member-icon\"></i>\n }\n <span class=\"member-label\">{{ member.Label }}</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Open Record button -->\n <div class=\"detail-footer\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecordClick()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n Open Record\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.scatter-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n/* SVG */\n.scatter-svg {\n width: 100%;\n height: 100%;\n cursor: grab;\n user-select: none;\n}\n\n.scatter-svg:active {\n cursor: grabbing;\n}\n\n/* Grid lines */\n.grid-lines line {\n stroke: var(--mj-border-subtle);\n stroke-width: 0.5;\n}\n\n/* Data points */\n.data-point {\n cursor: pointer;\n transition: opacity 0.15s ease, r 0.15s ease;\n}\n\n.data-point:hover {\n filter: brightness(1.2);\n}\n\n/* Selection rings */\n.selection-ring {\n pointer-events: none;\n transition: r 0.15s ease;\n}\n\n/* Disable transitions when AnimateTransitions is false */\n.no-transitions .data-point,\n.no-transitions .selection-ring {\n transition: none;\n}\n\n/* Loading overlay */\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-page) 80%, transparent);\n z-index: 5;\n}\n\n.loading-spinner {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n}\n\n.loading-spinner i {\n font-size: 2rem;\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.empty-state {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n gap: 8px;\n}\n\n.empty-state i {\n font-size: 3rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.empty-state p {\n font-size: 1rem;\n margin: 0;\n}\n\n.empty-hint {\n font-size: 0.8rem !important;\n opacity: 0.7;\n}\n\n/* Legend */\n.legend {\n position: absolute;\n bottom: 20px;\n left: 20px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(12px);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 18px;\n z-index: 3;\n}\n\n.legend-title {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 0.78rem;\n margin-bottom: 4px;\n color: var(--mj-text-primary);\n cursor: pointer;\n border-radius: 4px;\n padding: 2px 4px;\n margin-left: -4px;\n margin-right: -4px;\n transition: background 0.15s ease;\n}\n\n.legend-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.legend-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.legend-label {\n flex: 1;\n}\n\n.legend-edit-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6rem;\n border-radius: 4px;\n transition: color 0.15s ease;\n}\n\n.legend-item:hover .legend-edit-btn {\n display: inline-flex;\n}\n\n.legend-edit-btn:hover {\n color: var(--mj-brand-primary);\n}\n\n.legend-label-input {\n flex: 1;\n padding: 2px 4px;\n border: 1px solid var(--mj-border-focus);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.75rem;\n outline: none;\n min-width: 60px;\n}\n\n.legend-count {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n margin-left: auto;\n padding-left: 16px;\n}\n\n/* Cluster labels on chart */\n.cluster-label {\n font-size: 14px;\n font-weight: 600;\n opacity: 0.85;\n pointer-events: none;\n text-shadow:\n 0 0 6px var(--mj-bg-page),\n 0 0 12px var(--mj-bg-page),\n 1px 1px 2px var(--mj-bg-page);\n}\n\n/* Tooltip */\n.tooltip {\n position: fixed;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 96%, transparent);\n backdrop-filter: blur(16px);\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n padding: 14px 16px;\n width: 280px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n pointer-events: none;\n z-index: 50;\n}\n\n.tt-header {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n margin-bottom: 6px;\n}\n\n.tt-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.tt-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.tt-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n margin-top: 1px;\n}\n\n.tt-cluster {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.tt-fields {\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.tt-field-row {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-action {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n.tt-action i {\n margin-right: 4px;\n}\n\n/* ================================================================\n Detail Panel (right slide-in)\n ================================================================ */\n\n.detail-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 320px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(16px);\n border-left: 1px solid var(--mj-border-default);\n z-index: 10;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: -4px 0 24px color-mix(in srgb, var(--mj-bg-page) 30%, transparent);\n overflow: hidden;\n}\n\n.detail-panel-open {\n transform: translateX(0);\n}\n\n/* Header */\n.detail-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 16px 16px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-header-content {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.detail-icon {\n width: 2.25rem;\n height: 2.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.95rem;\n flex-shrink: 0;\n}\n\n.detail-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.detail-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n word-wrap: break-word;\n}\n\n.detail-entity-badge {\n display: inline-block;\n margin-top: 4px;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 0.68rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.detail-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n padding: 4px 6px;\n border-radius: 4px;\n transition: color 0.15s ease, background 0.15s ease;\n flex-shrink: 0;\n}\n\n.detail-close:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Cluster badge */\n.detail-cluster {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.detail-cluster-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n/* Metadata fields */\n.detail-fields {\n padding: 4px 16px 8px;\n overflow-y: auto;\n flex-shrink: 0;\n}\n\n.detail-field {\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-field:last-child {\n border-bottom: none;\n}\n\n.detail-field-key {\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n\n.detail-field-value {\n font-size: 0.8rem;\n color: var(--mj-text-primary);\n line-height: 1.4;\n word-wrap: break-word;\n}\n\n/* Cluster Members Section */\n.detail-members-section {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.detail-members-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: left;\n transition: background 0.15s ease;\n}\n\n.detail-members-toggle:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-members-toggle i {\n font-size: 0.65rem;\n width: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.detail-members-count {\n margin-left: auto;\n font-size: 0.7rem;\n font-weight: 400;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 1px 7px;\n border-radius: 8px;\n}\n\n.detail-members-list {\n flex: 1;\n overflow-y: auto;\n padding: 0 8px 8px;\n}\n\n.detail-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.12s ease;\n}\n\n.detail-member-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-member-row.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.detail-member-row .member-icon {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.detail-member-row.active .member-icon {\n color: var(--mj-brand-primary);\n}\n\n.member-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Footer */\n.detail-footer {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.detail-open-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.detail-open-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.detail-open-btn i {\n font-size: 0.75rem;\n}\n"] }]
1592
+ args: [{ standalone: false, selector: 'mj-cluster-scatter', encapsulation: ViewEncapsulation.None, template: "<div class=\"scatter-container\">\n <!-- Loading overlay -->\n @if (IsLoading) {\n <div class=\"loading-overlay\">\n <div class=\"loading-spinner\">\n <i class=\"fa-solid fa-circle-nodes fa-spin\"></i>\n <span>Computing clusters...</span>\n </div>\n </div>\n }\n\n <!-- Empty state -->\n @if (!IsLoading && Points.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-chart-scatter\"></i>\n <p>No data to display</p>\n <p class=\"empty-hint\">Configure and run clustering to see results</p>\n </div>\n }\n\n <!-- SVG scatter plot -->\n @if (Points.length > 0) {\n <svg #svgElement\n class=\"scatter-svg\"\n [class.no-transitions]=\"!AnimateTransitions\"\n [attr.viewBox]=\"ViewBoxString\"\n preserveAspectRatio=\"xMidYMid meet\"\n (wheel)=\"OnWheel($event)\"\n (mousedown)=\"OnMouseDown($event)\">\n\n <!-- Gradient definitions for cluster glow -->\n <defs>\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <radialGradient [attr.id]=\"'cluster-glow-' + centroid.ClusterId\"\n cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0.12\"/>\n <stop offset=\"100%\" [attr.stop-color]=\"centroid.Color\" stop-opacity=\"0\"/>\n </radialGradient>\n }\n </defs>\n\n <!-- Background grid lines -->\n <g class=\"grid-lines\">\n @for (y of HorizontalGridLines; track y) {\n <line [attr.x1]=\"0\" [attr.y1]=\"y\" [attr.x2]=\"1000\" [attr.y2]=\"y\"/>\n }\n @for (x of VerticalGridLines; track x) {\n <line [attr.x1]=\"x\" [attr.y1]=\"0\" [attr.x2]=\"x\" [attr.y2]=\"700\"/>\n }\n </g>\n\n <!-- Cluster glow + labels (2D only; centroids are 2D aesthetics) -->\n @if (!Is3D) {\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n <circle [attr.cx]=\"centroid.Cx\"\n [attr.cy]=\"centroid.Cy\"\n [attr.r]=\"centroid.R\"\n [attr.fill]=\"'url(#cluster-glow-' + centroid.ClusterId + ')'\"/>\n }\n @for (centroid of ClusterCentroids; track TrackCentroidBy($index, centroid)) {\n @if (GetClusterLabelById(centroid.ClusterId); as label) {\n <text class=\"cluster-label\"\n [attr.x]=\"centroid.Cx\"\n [attr.y]=\"centroid.Cy - centroid.R - 8\"\n text-anchor=\"middle\"\n [attr.fill]=\"centroid.Color\">{{ label }}</text>\n }\n }\n }\n\n <!-- Data points (RenderPoints is depth-sorted in 3D) -->\n @for (point of RenderPoints; track TrackPointBy($index, point)) {\n <circle class=\"data-point\"\n [attr.cx]=\"PX(point)\"\n [attr.cy]=\"PY(point)\"\n [attr.r]=\"GetPointRadius(point)\"\n [attr.fill]=\"GetPointColor(point)\"\n [attr.opacity]=\"GetPointOpacity(point)\"\n (mouseenter)=\"OnPointMouseEnter(point, $event)\"\n (mouseleave)=\"OnPointMouseLeave()\"\n (click)=\"OnPointClick(point); $event.stopPropagation()\"/>\n }\n\n <!-- 3D cluster labels (at projected centroids; follow rotation) -->\n @if (Is3D && ColorBy !== 'entity') {\n @for (lbl of ProjectedClusterLabels; track lbl.ClusterId) {\n @if (GetClusterLabelById(lbl.ClusterId); as text) {\n <text class=\"cluster-label cluster-label-3d\"\n [attr.x]=\"lbl.X\"\n [attr.y]=\"lbl.Y\"\n text-anchor=\"middle\"\n [attr.fill]=\"lbl.Color\">{{ text }}</text>\n }\n }\n }\n\n <!-- Selection rings for selected points -->\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsSelected(point)) {\n <circle class=\"selection-ring\"\n [attr.cx]=\"PX(point)\"\n [attr.cy]=\"PY(point)\"\n [attr.r]=\"HighlightRadius + 2\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2\"\n opacity=\"0.8\"/>\n }\n }\n\n <!-- Highlight ring for last-clicked point -->\n @if (HighlightedKey) {\n @for (point of Points; track TrackPointBy($index, point)) {\n @if (IsHighlighted(point)) {\n <circle [attr.cx]=\"PX(point)\"\n [attr.cy]=\"PY(point)\"\n [attr.r]=\"HighlightRadius + 4\"\n fill=\"none\"\n [attr.stroke]=\"GetPointColor(point)\"\n stroke-width=\"2.5\"\n opacity=\"0.9\"/>\n }\n }\n }\n </svg>\n }\n\n <!-- 3D orbit hint + reset -->\n @if (Points.length > 0 && Is3D) {\n <div class=\"threed-hint\">\n <i class=\"fa-solid fa-cube\"></i>\n <span>3D \u00B7 drag to rotate</span>\n <button class=\"threed-reset\" (click)=\"ResetRotation()\" title=\"Reset view\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n </button>\n </div>\n }\n\n <!-- Entity legend (multi-entity color mode) -->\n @if (ShowLegend && ColorBy === 'entity' && EntityLegend.length > 0) {\n <div class=\"legend\">\n <div class=\"legend-title\">Entities</div>\n @for (ent of EntityLegend; track ent.Name) {\n <div class=\"legend-item\">\n <div class=\"legend-dot\" [style.background]=\"ent.Color\"></div>\n <span class=\"legend-label\">{{ ent.Name }}</span>\n <span class=\"legend-count\">{{ ent.Count }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Legend overlay (cluster color mode) -->\n @if (ShowLegend && ColorBy !== 'entity' && Clusters.length > 0) {\n <div class=\"legend\">\n <div class=\"legend-title\">Clusters</div>\n @for (cluster of Clusters; track TrackClusterBy($index, cluster)) {\n <div class=\"legend-item\" (click)=\"OnClusterLegendClick(cluster)\">\n <div class=\"legend-dot\" [style.background]=\"cluster.Color\"></div>\n @if (EditingClusterId === cluster.Id) {\n <input class=\"legend-label-input\"\n [(ngModel)]=\"EditingLabelDraft\"\n (keydown)=\"OnLabelEditKeydown($event, cluster)\"\n (blur)=\"CommitLabelEdit(cluster)\"\n (click)=\"$event.stopPropagation()\"\n autofocus />\n } @else {\n <span class=\"legend-label\">{{ cluster.Label }}</span>\n <button class=\"legend-edit-btn\"\n title=\"Edit label\"\n (click)=\"StartLabelEdit(cluster, $event)\">\n <i class=\"fa-solid fa-pencil\"></i>\n </button>\n }\n <span class=\"legend-count\">{{ cluster.MemberCount }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Tooltip -->\n @if (ShowTooltip && HoveredPoint) {\n <div class=\"tooltip\"\n [style.left.px]=\"TooltipX\"\n [style.top.px]=\"TooltipY\">\n <mj-entity-card\n [EntityName]=\"EntityName\"\n [Record]=\"HoveredPoint.Metadata\"\n Variant=\"compact\">\n </mj-entity-card>\n <div class=\"tt-cluster\">\n <i class=\"fa-solid fa-circle\" style=\"font-size: 7px; vertical-align: middle\"></i>\n {{ GetClusterLabel(HoveredPoint) }}\n </div>\n <div class=\"tt-action\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Click to view details\n </div>\n </div>\n }\n\n <!-- Detail Panel (slide-in from right) -->\n <div class=\"detail-panel\" [class.detail-panel-open]=\"ShowDetailPanel && SelectedPoint\">\n @if (SelectedPoint) {\n <!-- Panel Header -->\n <div class=\"detail-header\">\n <div class=\"detail-header-content\">\n @if (SelectedPoint.Metadata?.['EntityIcon']) {\n <div class=\"detail-icon\">\n <i [class]=\"SelectedPoint.Metadata['EntityIcon']\"></i>\n </div>\n }\n <div class=\"detail-header-text\">\n <div class=\"detail-name\">{{ SelectedPoint.Label }}</div>\n @if (SelectedPoint.Metadata?.['Entity']) {\n <span class=\"detail-entity-badge\">{{ SelectedPoint.Metadata['Entity'] }}</span>\n }\n </div>\n </div>\n <button class=\"detail-close\" (click)=\"CloseDetailPanel()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n\n <!-- Cluster badge -->\n <div class=\"detail-cluster\">\n <span class=\"detail-cluster-dot\"\n [style.background]=\"GetClusterColor(SelectedPoint.ClusterId)\"></span>\n {{ GetClusterLabel(SelectedPoint) }}\n </div>\n\n <!-- Metadata fields via entity card -->\n <mj-entity-card\n [EntityName]=\"EntityName\"\n [Record]=\"SelectedPoint.Metadata\"\n Variant=\"detail\"\n CssClass=\"detail-entity-card\">\n </mj-entity-card>\n\n <!-- Cluster Members -->\n <div class=\"detail-members-section\">\n <button class=\"detail-members-toggle\" (click)=\"ToggleClusterMembers()\">\n <i class=\"fa-solid\"\n [class.fa-chevron-down]=\"ClusterMembersExpanded\"\n [class.fa-chevron-right]=\"!ClusterMembersExpanded\"></i>\n <span>Cluster Members</span>\n <span class=\"detail-members-count\">{{ ClusterMembers.length }}</span>\n </button>\n\n @if (ClusterMembersExpanded) {\n <div class=\"detail-members-list\">\n @for (member of ClusterMembers; track TrackMemberBy($index, member)) {\n <div class=\"detail-member-row\"\n [class.active]=\"member.VectorKey === SelectedPoint.VectorKey\"\n (click)=\"OnClusterMemberClick(member)\">\n @if (member.Metadata?.['EntityIcon']) {\n <i [class]=\"member.Metadata['EntityIcon']\" class=\"member-icon\"></i>\n }\n <span class=\"member-label\">{{ member.Label }}</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Open Record button -->\n <div class=\"detail-footer\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecordClick()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n Open Record\n </button>\n </div>\n }\n </div>\n</div>\n", styles: [":host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.scatter-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n/* SVG */\n.scatter-svg {\n width: 100%;\n height: 100%;\n cursor: grab;\n user-select: none;\n}\n\n.scatter-svg:active {\n cursor: grabbing;\n}\n\n/* Grid lines */\n.grid-lines line {\n stroke: var(--mj-border-subtle);\n stroke-width: 0.5;\n}\n\n/* Data points */\n.data-point {\n cursor: pointer;\n transition: opacity 0.15s ease, r 0.15s ease;\n}\n\n.data-point:hover {\n filter: brightness(1.2);\n}\n\n/* Selection rings */\n.selection-ring {\n pointer-events: none;\n transition: r 0.15s ease;\n}\n\n/* Disable transitions when AnimateTransitions is false */\n.no-transitions .data-point,\n.no-transitions .selection-ring {\n transition: none;\n}\n\n/* Loading overlay */\n.loading-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-page) 80%, transparent);\n z-index: 5;\n}\n\n.loading-spinner {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n color: var(--mj-text-secondary);\n font-size: 0.9rem;\n}\n\n.loading-spinner i {\n font-size: 2rem;\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.empty-state {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted);\n gap: 8px;\n}\n\n.empty-state i {\n font-size: 3rem;\n margin-bottom: 8px;\n opacity: 0.4;\n}\n\n.empty-state p {\n font-size: 1rem;\n margin: 0;\n}\n\n.empty-hint {\n font-size: 0.8rem !important;\n opacity: 0.7;\n}\n\n/* Legend */\n.legend {\n position: absolute;\n bottom: 20px;\n left: 20px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(12px);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 14px 18px;\n z-index: 3;\n}\n\n.legend-title {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n}\n\n.legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 0.78rem;\n margin-bottom: 4px;\n color: var(--mj-text-primary);\n cursor: pointer;\n border-radius: 4px;\n padding: 2px 4px;\n margin-left: -4px;\n margin-right: -4px;\n transition: background 0.15s ease;\n}\n\n.legend-item:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.legend-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.legend-label {\n flex: 1;\n}\n\n.legend-edit-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n padding: 0;\n border: none;\n background: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: 0.6rem;\n border-radius: 4px;\n transition: color 0.15s ease;\n}\n\n.legend-item:hover .legend-edit-btn {\n display: inline-flex;\n}\n\n.legend-edit-btn:hover {\n color: var(--mj-brand-primary);\n}\n\n.legend-label-input {\n flex: 1;\n padding: 2px 4px;\n border: 1px solid var(--mj-border-focus);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 0.75rem;\n outline: none;\n min-width: 60px;\n}\n\n.legend-count {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n margin-left: auto;\n padding-left: 16px;\n}\n\n/* Cluster labels on chart */\n.cluster-label {\n font-size: 14px;\n font-weight: 600;\n opacity: 0.85;\n pointer-events: none;\n text-shadow:\n 0 0 6px var(--mj-bg-page),\n 0 0 12px var(--mj-bg-page),\n 1px 1px 2px var(--mj-bg-page);\n}\n\n/* Tooltip */\n.tooltip {\n position: fixed;\n background: color-mix(in srgb, var(--mj-bg-surface-elevated) 96%, transparent);\n backdrop-filter: blur(16px);\n border: 1px solid var(--mj-border-strong);\n border-radius: 10px;\n padding: 14px 16px;\n width: 280px;\n box-shadow: 0 12px 40px color-mix(in srgb, var(--mj-bg-page) 40%, transparent);\n pointer-events: none;\n z-index: 50;\n}\n\n.tt-header {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n margin-bottom: 6px;\n}\n\n.tt-icon {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n flex-shrink: 0;\n}\n\n.tt-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.tt-name {\n font-size: 0.85rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n margin-top: 1px;\n}\n\n.tt-cluster {\n font-size: 0.72rem;\n color: var(--mj-brand-primary);\n margin-bottom: 6px;\n}\n\n.tt-fields {\n font-size: 0.72rem;\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.tt-field-row {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.tt-action {\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--mj-border-default);\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n.tt-action i {\n margin-right: 4px;\n}\n\n/* ================================================================\n Detail Panel (right slide-in)\n ================================================================ */\n\n.detail-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 320px;\n background: color-mix(in srgb, var(--mj-bg-surface) 92%, transparent);\n backdrop-filter: blur(16px);\n border-left: 1px solid var(--mj-border-default);\n z-index: 10;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: -4px 0 24px color-mix(in srgb, var(--mj-bg-page) 30%, transparent);\n overflow: hidden;\n}\n\n.detail-panel-open {\n transform: translateX(0);\n}\n\n/* Header */\n.detail-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 16px 16px 12px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-header-content {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.detail-icon {\n width: 2.25rem;\n height: 2.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.95rem;\n flex-shrink: 0;\n}\n\n.detail-header-text {\n min-width: 0;\n flex: 1;\n}\n\n.detail-name {\n font-size: 0.95rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n word-wrap: break-word;\n}\n\n.detail-entity-badge {\n display: inline-block;\n margin-top: 4px;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 0.68rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.detail-close {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 0.85rem;\n padding: 4px 6px;\n border-radius: 4px;\n transition: color 0.15s ease, background 0.15s ease;\n flex-shrink: 0;\n}\n\n.detail-close:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n\n/* Cluster badge */\n.detail-cluster {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 0.78rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n}\n\n.detail-cluster-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n/* Metadata fields */\n.detail-fields {\n padding: 4px 16px 8px;\n overflow-y: auto;\n flex-shrink: 0;\n}\n\n.detail-field {\n padding: 6px 0;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.detail-field:last-child {\n border-bottom: none;\n}\n\n.detail-field-key {\n font-size: 0.68rem;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n margin-bottom: 2px;\n}\n\n.detail-field-value {\n font-size: 0.8rem;\n color: var(--mj-text-primary);\n line-height: 1.4;\n word-wrap: break-word;\n}\n\n/* Cluster Members Section */\n.detail-members-section {\n flex: 1;\n min-height: 0;\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-default);\n}\n\n.detail-members-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 16px;\n background: none;\n border: none;\n cursor: pointer;\n font-size: 0.78rem;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: left;\n transition: background 0.15s ease;\n}\n\n.detail-members-toggle:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-members-toggle i {\n font-size: 0.65rem;\n width: 12px;\n text-align: center;\n color: var(--mj-text-muted);\n}\n\n.detail-members-count {\n margin-left: auto;\n font-size: 0.7rem;\n font-weight: 400;\n color: var(--mj-text-muted);\n background: var(--mj-bg-surface-sunken);\n padding: 1px 7px;\n border-radius: 8px;\n}\n\n.detail-members-list {\n flex: 1;\n overflow-y: auto;\n padding: 0 8px 8px;\n}\n\n.detail-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.12s ease;\n}\n\n.detail-member-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.detail-member-row.active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-weight: 500;\n}\n\n.detail-member-row .member-icon {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n width: 16px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.detail-member-row.active .member-icon {\n color: var(--mj-brand-primary);\n}\n\n.member-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Footer */\n.detail-footer {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.detail-open-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.detail-open-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.detail-open-btn i {\n font-size: 0.75rem;\n}\n\n/* 3D orbit hint + reset */\n.threed-hint {\n position: absolute;\n top: 12px;\n right: 12px;\n z-index: 6;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 5px 8px 5px 10px;\n background: color-mix(in srgb, var(--mj-bg-surface) 88%, transparent);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n backdrop-filter: blur(4px);\n}\n.threed-hint > i { color: var(--mj-brand-primary); }\n.threed-reset {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 6px;\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n.threed-reset:hover { color: var(--mj-brand-primary); border-color: var(--mj-brand-primary); }\n\n/* 3D cluster labels \u2014 halo for readability over points */\n.cluster-label-3d {\n font-weight: 700;\n font-size: 13px;\n paint-order: stroke;\n stroke: var(--mj-bg-surface);\n stroke-width: 3px;\n stroke-linejoin: round;\n pointer-events: none;\n}\n"] }]
1345
1593
  }], null, { Points: [{
1346
1594
  type: Input
1347
1595
  }], Clusters: [{
1348
1596
  type: Input
1597
+ }], ColorBy: [{
1598
+ type: Input
1349
1599
  }], IsLoading: [{
1350
1600
  type: Input
1351
1601
  }], DotRadius: [{