@memberjunction/ng-clustering 5.39.0 → 5.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/cluster-view-type.test.d.ts +2 -0
- package/dist/__tests__/cluster-view-type.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-view-type.test.js +63 -0
- package/dist/__tests__/cluster-view-type.test.js.map +1 -0
- package/dist/lib/cluster-config-panel.component.d.ts +39 -1
- package/dist/lib/cluster-config-panel.component.d.ts.map +1 -1
- package/dist/lib/cluster-config-panel.component.js +287 -88
- package/dist/lib/cluster-config-panel.component.js.map +1 -1
- package/dist/lib/cluster-scatter.component.d.ts +53 -1
- package/dist/lib/cluster-scatter.component.d.ts.map +1 -1
- package/dist/lib/cluster-scatter.component.js +385 -135
- package/dist/lib/cluster-scatter.component.js.map +1 -1
- package/dist/lib/clustering.module.d.ts +7 -4
- package/dist/lib/clustering.module.d.ts.map +1 -1
- package/dist/lib/clustering.module.js +20 -3
- package/dist/lib/clustering.module.js.map +1 -1
- package/dist/lib/clustering.service.d.ts +63 -3
- package/dist/lib/clustering.service.d.ts.map +1 -1
- package/dist/lib/clustering.service.js +169 -48
- package/dist/lib/clustering.service.js.map +1 -1
- package/dist/lib/clustering.types.d.ts +17 -2
- package/dist/lib/clustering.types.d.ts.map +1 -1
- package/dist/lib/clustering.types.js +3 -0
- package/dist/lib/clustering.types.js.map +1 -1
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts +31 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js +185 -0
- package/dist/lib/view-type/cluster-view-prop-sheet.component.js.map +1 -0
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts +143 -0
- package/dist/lib/view-type/cluster-view-renderer.component.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-renderer.component.js +508 -0
- package/dist/lib/view-type/cluster-view-renderer.component.js.map +1 -0
- package/dist/lib/view-type/cluster-view-type.d.ts +35 -0
- package/dist/lib/view-type/cluster-view-type.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view-type.js +51 -0
- package/dist/lib/view-type/cluster-view-type.js.map +1 -0
- package/dist/lib/view-type/cluster-view.types.d.ts +31 -0
- package/dist/lib/view-type/cluster-view.types.d.ts.map +1 -0
- package/dist/lib/view-type/cluster-view.types.js +29 -0
- package/dist/lib/view-type/cluster-view.types.js.map +1 -0
- package/dist/lib/view-type/entity-document-availability.engine.d.ts +80 -0
- package/dist/lib/view-type/entity-document-availability.engine.d.ts.map +1 -0
- package/dist/lib/view-type/entity-document-availability.engine.js +149 -0
- package/dist/lib/view-type/entity-document-availability.engine.js.map +1 -0
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +11 -3
|
@@ -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",
|
|
38
|
-
i0.ɵɵelement(2, "i",
|
|
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",
|
|
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",
|
|
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",
|
|
56
|
-
i0.ɵɵelement(1, "stop",
|
|
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
|
|
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
|
|
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",
|
|
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
|
|
99
|
-
i0.ɵɵconditionalCreate(0,
|
|
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
|
|
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.ɵɵ
|
|
113
|
+
i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
|
|
114
|
+
i0.ɵɵadvance(2);
|
|
115
|
+
i0.ɵɵrepeater(ctx_r1.ClusterCentroids);
|
|
105
116
|
} }
|
|
106
|
-
function
|
|
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",
|
|
110
|
-
i0.ɵɵlistener("mouseenter", function
|
|
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
|
|
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
|
|
128
|
+
function ClusterScatterComponent_Conditional_3_Conditional_13_For_1_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
118
129
|
i0.ɵɵnamespaceSVG();
|
|
119
|
-
i0.ɵɵ
|
|
130
|
+
i0.ɵɵelementStart(0, "text", 21);
|
|
131
|
+
i0.ɵɵtext(1);
|
|
132
|
+
i0.ɵɵelementEnd();
|
|
120
133
|
} if (rf & 2) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
i0.ɵɵ
|
|
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
|
|
126
|
-
i0.ɵɵconditionalCreate(0,
|
|
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.ɵɵ
|
|
151
|
+
i0.ɵɵrepeater(ctx_r1.ProjectedClusterLabels);
|
|
131
152
|
} }
|
|
132
|
-
function
|
|
153
|
+
function ClusterScatterComponent_Conditional_3_For_15_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
133
154
|
i0.ɵɵnamespaceSVG();
|
|
134
|
-
i0.ɵɵelement(0, "circle",
|
|
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(
|
|
138
|
-
i0.ɵɵattribute("cx", 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
|
|
141
|
-
i0.ɵɵconditionalCreate(0,
|
|
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(
|
|
181
|
+
i0.ɵɵconditional(ctx_r1.IsHighlighted(point_r12) ? 0 : -1);
|
|
146
182
|
} }
|
|
147
|
-
function
|
|
148
|
-
i0.ɵɵrepeaterCreate(0,
|
|
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",
|
|
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",
|
|
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",
|
|
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.ɵɵ
|
|
166
|
-
i0.ɵɵrepeaterCreate(
|
|
167
|
-
i0.ɵɵ
|
|
168
|
-
i0.ɵɵrepeaterCreate(
|
|
169
|
-
i0.ɵɵconditionalCreate(
|
|
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.ɵɵ
|
|
183
|
-
i0.ɵɵadvance(
|
|
184
|
-
i0.ɵɵrepeater(ctx_r1.
|
|
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.ɵɵ
|
|
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.ɵɵ
|
|
256
|
+
i0.ɵɵtextInterpolate(ent_r14.Count);
|
|
191
257
|
} }
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
i0.ɵɵ
|
|
195
|
-
i0.ɵɵ
|
|
196
|
-
i0.ɵɵ
|
|
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
|
|
203
|
-
const
|
|
204
|
-
i0.ɵɵelementStart(0, "span",
|
|
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",
|
|
208
|
-
i0.ɵɵlistener("click", function
|
|
209
|
-
i0.ɵɵelement(3, "i",
|
|
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
|
|
289
|
+
const cluster_r16 = i0.ɵɵnextContext().$implicit;
|
|
213
290
|
i0.ɵɵadvance();
|
|
214
|
-
i0.ɵɵtextInterpolate(
|
|
291
|
+
i0.ɵɵtextInterpolate(cluster_r16.Label);
|
|
215
292
|
} }
|
|
216
|
-
function
|
|
217
|
-
const
|
|
218
|
-
i0.ɵɵelementStart(0, "div",
|
|
219
|
-
i0.ɵɵlistener("click", function
|
|
220
|
-
i0.ɵɵelement(1, "div",
|
|
221
|
-
i0.ɵɵconditionalCreate(2,
|
|
222
|
-
i0.ɵɵelementStart(4, "span",
|
|
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
|
|
303
|
+
const cluster_r16 = ctx.$implicit;
|
|
227
304
|
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
228
305
|
i0.ɵɵadvance();
|
|
229
|
-
i0.ɵɵstyleProp("background",
|
|
306
|
+
i0.ɵɵstyleProp("background", cluster_r16.Color);
|
|
230
307
|
i0.ɵɵadvance();
|
|
231
|
-
i0.ɵɵconditional(ctx_r1.EditingClusterId ===
|
|
308
|
+
i0.ɵɵconditional(ctx_r1.EditingClusterId === cluster_r16.Id ? 2 : 3);
|
|
232
309
|
i0.ɵɵadvance(3);
|
|
233
|
-
i0.ɵɵtextInterpolate(
|
|
310
|
+
i0.ɵɵtextInterpolate(cluster_r16.MemberCount);
|
|
234
311
|
} }
|
|
235
|
-
function
|
|
236
|
-
i0.ɵɵelementStart(0, "div",
|
|
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,
|
|
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
|
|
247
|
-
i0.ɵɵelementStart(0, "div",
|
|
248
|
-
i0.ɵɵelement(1, "mj-entity-card",
|
|
249
|
-
i0.ɵɵelementStart(2, "div",
|
|
250
|
-
i0.ɵɵelement(3, "i",
|
|
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",
|
|
254
|
-
i0.ɵɵelement(6, "i",
|
|
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
|
|
266
|
-
i0.ɵɵelementStart(0, "div",
|
|
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
|
|
275
|
-
i0.ɵɵelementStart(0, "span",
|
|
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
|
|
284
|
-
i0.ɵɵelement(0, "i",
|
|
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
|
|
287
|
-
i0.ɵɵclassMap(
|
|
363
|
+
const member_r21 = i0.ɵɵnextContext().$implicit;
|
|
364
|
+
i0.ɵɵclassMap(member_r21.Metadata["EntityIcon"]);
|
|
288
365
|
} }
|
|
289
|
-
function
|
|
290
|
-
const
|
|
291
|
-
i0.ɵɵelementStart(0, "div",
|
|
292
|
-
i0.ɵɵlistener("click", function
|
|
293
|
-
i0.ɵɵconditionalCreate(1,
|
|
294
|
-
i0.ɵɵelementStart(2, "span",
|
|
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
|
|
375
|
+
const member_r21 = ctx.$implicit;
|
|
299
376
|
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
300
|
-
i0.ɵɵclassProp("active",
|
|
377
|
+
i0.ɵɵclassProp("active", member_r21.VectorKey === ctx_r1.SelectedPoint.VectorKey);
|
|
301
378
|
i0.ɵɵadvance();
|
|
302
|
-
i0.ɵɵconditional((
|
|
379
|
+
i0.ɵɵconditional((member_r21.Metadata == null ? null : member_r21.Metadata["EntityIcon"]) ? 1 : -1);
|
|
303
380
|
i0.ɵɵadvance(2);
|
|
304
|
-
i0.ɵɵtextInterpolate(
|
|
381
|
+
i0.ɵɵtextInterpolate(member_r21.Label);
|
|
305
382
|
} }
|
|
306
|
-
function
|
|
307
|
-
i0.ɵɵelementStart(0, "div",
|
|
308
|
-
i0.ɵɵrepeaterCreate(1,
|
|
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
|
|
316
|
-
const
|
|
317
|
-
i0.ɵɵelementStart(0, "div",
|
|
318
|
-
i0.ɵɵconditionalCreate(2,
|
|
319
|
-
i0.ɵɵelementStart(3, "div",
|
|
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,
|
|
399
|
+
i0.ɵɵconditionalCreate(6, ClusterScatterComponent_Conditional_9_Conditional_6_Template, 2, 1, "span", 48);
|
|
323
400
|
i0.ɵɵelementEnd()();
|
|
324
|
-
i0.ɵɵelementStart(7, "button",
|
|
325
|
-
i0.ɵɵlistener("click", function
|
|
326
|
-
i0.ɵɵelement(8, "i",
|
|
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",
|
|
329
|
-
i0.ɵɵelement(10, "span",
|
|
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",
|
|
333
|
-
i0.ɵɵelementStart(13, "div",
|
|
334
|
-
i0.ɵɵlistener("click", function
|
|
335
|
-
i0.ɵɵelement(15, "i",
|
|
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",
|
|
416
|
+
i0.ɵɵelementStart(18, "span", 57);
|
|
340
417
|
i0.ɵɵtext(19);
|
|
341
418
|
i0.ɵɵelementEnd()();
|
|
342
|
-
i0.ɵɵconditionalCreate(20,
|
|
419
|
+
i0.ɵɵconditionalCreate(20, ClusterScatterComponent_Conditional_9_Conditional_20_Template, 3, 0, "div", 58);
|
|
343
420
|
i0.ɵɵelementEnd();
|
|
344
|
-
i0.ɵɵelementStart(21, "div",
|
|
345
|
-
i0.ɵɵlistener("click", function
|
|
346
|
-
i0.ɵɵelement(23, "i",
|
|
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
|
|
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:
|
|
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,
|
|
1320
|
-
i0.ɵɵconditionalCreate(4, ClusterScatterComponent_Conditional_4_Template,
|
|
1321
|
-
i0.ɵɵconditionalCreate(5, ClusterScatterComponent_Conditional_5_Template,
|
|
1322
|
-
i0.ɵɵ
|
|
1323
|
-
i0.ɵɵconditionalCreate(7, ClusterScatterComponent_Conditional_7_Template,
|
|
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.
|
|
1577
|
+
i0.ɵɵconditional(ctx.Points.length > 0 && ctx.Is3D ? 4 : -1);
|
|
1334
1578
|
i0.ɵɵadvance();
|
|
1335
|
-
i0.ɵɵconditional(ctx.
|
|
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 ?
|
|
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: [{
|