@qe-mcp/server-ena 0.1.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/.claude/launch.json +11 -0
- package/.gitlab-ci.yml +88 -0
- package/README.md +232 -0
- package/hooks/ena-privacy-gate.mjs +29 -0
- package/package.json +27 -0
- package/qeviz-init.js +11 -0
- package/qeviz-widget.html +50 -0
- package/qeviz.umd.js +3511 -0
- package/server.js +1234 -0
- package/skill/ena-analysis/SKILL.md +82 -0
- package/test-mcp.mjs +189 -0
- package/test-render.mjs +93 -0
package/qeviz.umd.js
ADDED
|
@@ -0,0 +1,3511 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.QEViz = {}));
|
|
3
|
+
})(this, (function(exports2) {
|
|
4
|
+
"use strict";var __defProp = Object.defineProperty;
|
|
5
|
+
var __typeError = (msg) => {
|
|
6
|
+
throw TypeError(msg);
|
|
7
|
+
};
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
10
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
11
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
12
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
13
|
+
|
|
14
|
+
var _rowNumber, _rowName, _names, _parent, _classes, _name, _type, _rowNames, _parent2, _names2, _rowNames2, _classes2, _flipped, _names3, _edge, _with_arrow, _arrow;
|
|
15
|
+
const _DataFrameRow = class _DataFrameRow extends Array {
|
|
16
|
+
constructor(names, values, rowName, parent, index) {
|
|
17
|
+
super(...values);
|
|
18
|
+
__privateAdd(this, _rowNumber);
|
|
19
|
+
__privateAdd(this, _rowName);
|
|
20
|
+
__privateAdd(this, _names);
|
|
21
|
+
__privateAdd(this, _parent);
|
|
22
|
+
__privateAdd(this, _classes);
|
|
23
|
+
__privateSet(this, _names, names);
|
|
24
|
+
__privateSet(this, _rowName, rowName);
|
|
25
|
+
__privateSet(this, _parent, parent);
|
|
26
|
+
__privateSet(this, _classes, parent == null ? void 0 : parent.classes);
|
|
27
|
+
__privateSet(this, _rowNumber, index);
|
|
28
|
+
}
|
|
29
|
+
get parent() {
|
|
30
|
+
return __privateGet(this, _parent);
|
|
31
|
+
}
|
|
32
|
+
get names() {
|
|
33
|
+
return __privateGet(this, _names);
|
|
34
|
+
}
|
|
35
|
+
get rowName() {
|
|
36
|
+
return __privateGet(this, _rowName);
|
|
37
|
+
}
|
|
38
|
+
get types() {
|
|
39
|
+
if (!__privateGet(this, _parent)) throw new Error("No parent DataFrame attached to this row.");
|
|
40
|
+
return __privateGet(this, _parent).types;
|
|
41
|
+
}
|
|
42
|
+
get classes() {
|
|
43
|
+
return __privateGet(this, _classes);
|
|
44
|
+
}
|
|
45
|
+
get rowNumber() {
|
|
46
|
+
return __privateGet(this, _rowNumber);
|
|
47
|
+
}
|
|
48
|
+
col(wh) {
|
|
49
|
+
if (!__privateGet(this, _parent)) throw new Error("No parent DataFrame attached to this row.");
|
|
50
|
+
return __privateGet(this, _parent).col(wh);
|
|
51
|
+
}
|
|
52
|
+
cols(...wh) {
|
|
53
|
+
if (!__privateGet(this, _parent)) throw new Error("No parent DataFrame attached to this row.");
|
|
54
|
+
return __privateGet(this, _parent).cols(...wh);
|
|
55
|
+
}
|
|
56
|
+
filter(predicate) {
|
|
57
|
+
console.error("DataFrameRow does not currently support filtering.");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
_rowNumber = new WeakMap();
|
|
61
|
+
_rowName = new WeakMap();
|
|
62
|
+
_names = new WeakMap();
|
|
63
|
+
_parent = new WeakMap();
|
|
64
|
+
_classes = new WeakMap();
|
|
65
|
+
__name(_DataFrameRow, "DataFrameRow");
|
|
66
|
+
let DataFrameRow = _DataFrameRow;
|
|
67
|
+
var DataFrameColumnType = /* @__PURE__ */ ((DataFrameColumnType2) => {
|
|
68
|
+
DataFrameColumnType2["Character"] = "character";
|
|
69
|
+
DataFrameColumnType2["Double"] = "double";
|
|
70
|
+
DataFrameColumnType2["Logical"] = "logical";
|
|
71
|
+
DataFrameColumnType2["Integer"] = "integer";
|
|
72
|
+
return DataFrameColumnType2;
|
|
73
|
+
})(DataFrameColumnType || {});
|
|
74
|
+
const _DataFrameColumn = class _DataFrameColumn extends Array {
|
|
75
|
+
constructor(name, column, rowNames, parent) {
|
|
76
|
+
super(column.length);
|
|
77
|
+
__privateAdd(this, _name);
|
|
78
|
+
__privateAdd(this, _type);
|
|
79
|
+
__privateAdd(this, _rowNames);
|
|
80
|
+
__privateAdd(this, _parent2);
|
|
81
|
+
for (let i = 0; i < column.length; i++) {
|
|
82
|
+
this[i] = column[i];
|
|
83
|
+
}
|
|
84
|
+
__privateSet(this, _name, name);
|
|
85
|
+
if (column.type !== void 0) {
|
|
86
|
+
__privateSet(this, _type, column.type);
|
|
87
|
+
} else {
|
|
88
|
+
if (column.every((v) => typeof v === "string" || v === null || v === void 0)) {
|
|
89
|
+
__privateSet(this, _type, "character");
|
|
90
|
+
} else if (column.every((v) => Number.isInteger(v) || v === null || v === void 0)) {
|
|
91
|
+
if (column.every((v) => v === 0 || v === 1 || v === null || v === void 0)) {
|
|
92
|
+
__privateSet(this, _type, "logical");
|
|
93
|
+
} else {
|
|
94
|
+
__privateSet(this, _type, "integer");
|
|
95
|
+
}
|
|
96
|
+
} else if (column.every((v) => typeof v === "number" || v === null || v === void 0)) {
|
|
97
|
+
__privateSet(this, _type, "double");
|
|
98
|
+
} else if (column.every((v) => typeof v === "boolean" || v === null || v === void 0)) {
|
|
99
|
+
__privateSet(this, _type, "logical");
|
|
100
|
+
} else {
|
|
101
|
+
__privateSet(this, _type, "character");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
__privateSet(this, _parent2, parent);
|
|
105
|
+
if (rowNames && rowNames.length !== this.length) {
|
|
106
|
+
throw new Error("Row names length must match the number of rows in the DataFrameColumn.");
|
|
107
|
+
}
|
|
108
|
+
if (rowNames && rowNames.length > 0) {
|
|
109
|
+
__privateSet(this, _rowNames, rowNames);
|
|
110
|
+
} else {
|
|
111
|
+
__privateSet(this, _rowNames, void 0);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
get name() {
|
|
115
|
+
return __privateGet(this, _name);
|
|
116
|
+
}
|
|
117
|
+
get type() {
|
|
118
|
+
return __privateGet(this, _type);
|
|
119
|
+
}
|
|
120
|
+
get rowNames() {
|
|
121
|
+
return __privateGet(this, _rowNames);
|
|
122
|
+
}
|
|
123
|
+
row(wh) {
|
|
124
|
+
if (!__privateGet(this, _parent2)) throw new Error("No parent DataFrame attached to this column.");
|
|
125
|
+
return __privateGet(this, _parent2).row(wh);
|
|
126
|
+
}
|
|
127
|
+
rows(...wh) {
|
|
128
|
+
if (!__privateGet(this, _parent2)) throw new Error("No parent DataFrame attached to this column.");
|
|
129
|
+
return __privateGet(this, _parent2).rows(...wh);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
_name = new WeakMap();
|
|
133
|
+
_type = new WeakMap();
|
|
134
|
+
_rowNames = new WeakMap();
|
|
135
|
+
_parent2 = new WeakMap();
|
|
136
|
+
__name(_DataFrameColumn, "DataFrameColumn");
|
|
137
|
+
let DataFrameColumn = _DataFrameColumn;
|
|
138
|
+
const _DataFrame = class _DataFrame extends Array {
|
|
139
|
+
constructor(names, colValues, rowNames, classes) {
|
|
140
|
+
if (!names || !colValues) {
|
|
141
|
+
colValues = [];
|
|
142
|
+
names = [];
|
|
143
|
+
}
|
|
144
|
+
if (colValues.map((c) => c.length).every((l) => l === names.length)) {
|
|
145
|
+
const transposed = [];
|
|
146
|
+
for (let i = 0; i < names.length; i++) {
|
|
147
|
+
const col = [];
|
|
148
|
+
for (let j = 0; j < colValues.length; j++) {
|
|
149
|
+
col.push(colValues[j][i]);
|
|
150
|
+
}
|
|
151
|
+
transposed.push(col);
|
|
152
|
+
}
|
|
153
|
+
colValues = transposed;
|
|
154
|
+
}
|
|
155
|
+
super(colValues.length);
|
|
156
|
+
__privateAdd(this, _names2);
|
|
157
|
+
__privateAdd(this, _rowNames2);
|
|
158
|
+
__privateAdd(this, _classes2);
|
|
159
|
+
colValues.forEach((col, index) => {
|
|
160
|
+
if (col instanceof DataFrameColumn) {
|
|
161
|
+
this[index] = col;
|
|
162
|
+
} else {
|
|
163
|
+
this[index] = new DataFrameColumn(names[index], col, rowNames, this);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
__privateSet(this, _names2, names);
|
|
167
|
+
if (rowNames && rowNames.length !== this[0].length) {
|
|
168
|
+
throw new Error("Row names length must match the number of rows in the DataFrame.");
|
|
169
|
+
}
|
|
170
|
+
if (rowNames && rowNames.length > 0) {
|
|
171
|
+
__privateSet(this, _rowNames2, rowNames);
|
|
172
|
+
}
|
|
173
|
+
if (classes && classes.length !== this.length) {
|
|
174
|
+
throw new Error("Classes length must match the number of columns in the DataFrame.");
|
|
175
|
+
}
|
|
176
|
+
if (classes && classes.length > 0) {
|
|
177
|
+
__privateSet(this, _classes2, classes);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
get classes() {
|
|
181
|
+
return __privateGet(this, _classes2);
|
|
182
|
+
}
|
|
183
|
+
getColumnByName(name) {
|
|
184
|
+
const idx = __privateGet(this, _names2).indexOf(name);
|
|
185
|
+
return idx >= 0 ? this[idx] : void 0;
|
|
186
|
+
}
|
|
187
|
+
getRowByIndex(index) {
|
|
188
|
+
if (this && this.length === 0) {
|
|
189
|
+
console.warn("DataFrame is empty, cannot get row by index", index);
|
|
190
|
+
return new DataFrameRow(__privateGet(this, _names2), [], void 0, this, index);
|
|
191
|
+
}
|
|
192
|
+
const result_vals = [];
|
|
193
|
+
for (let col of this) {
|
|
194
|
+
let v = col[index];
|
|
195
|
+
result_vals.push(v);
|
|
196
|
+
}
|
|
197
|
+
return new DataFrameRow(__privateGet(this, _names2), result_vals, __privateGet(this, _rowNames2) ? __privateGet(this, _rowNames2)[index] : void 0, this, index);
|
|
198
|
+
}
|
|
199
|
+
getRowByName(name) {
|
|
200
|
+
if (!__privateGet(this, _rowNames2)) return void 0;
|
|
201
|
+
const index = __privateGet(this, _rowNames2).indexOf(name);
|
|
202
|
+
if (index === -1) return void 0;
|
|
203
|
+
return this.getRowByIndex(index);
|
|
204
|
+
}
|
|
205
|
+
get types() {
|
|
206
|
+
return this.map((col) => col.type);
|
|
207
|
+
}
|
|
208
|
+
type(wh) {
|
|
209
|
+
var _a;
|
|
210
|
+
if (typeof wh === "number") {
|
|
211
|
+
return (_a = this[wh]) == null ? void 0 : _a.type;
|
|
212
|
+
} else if (typeof wh === "string") {
|
|
213
|
+
const col = this.getColumnByName(wh);
|
|
214
|
+
return col ? col.type : void 0;
|
|
215
|
+
}
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
// row(wh: string | number): DataFrameRow | undefined {
|
|
219
|
+
row(wh) {
|
|
220
|
+
if (typeof wh === "number") {
|
|
221
|
+
return this.getRowByIndex(wh);
|
|
222
|
+
} else if (typeof wh === "string") {
|
|
223
|
+
return this.getRowByName(wh);
|
|
224
|
+
}
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
227
|
+
// rows(...wh: (string | number)[]): DataFrame {
|
|
228
|
+
rows(...wh) {
|
|
229
|
+
let selectors, selectedRows = [], selectedRowNames = [];
|
|
230
|
+
if (wh.length === 0) {
|
|
231
|
+
const numRows = this[0].length;
|
|
232
|
+
selectedRows = Array.from({ length: numRows }, (_, i) => this.getRowByIndex(i));
|
|
233
|
+
selectedRowNames = __privateGet(this, _rowNames2);
|
|
234
|
+
} else {
|
|
235
|
+
if (wh.length === 1 && Array.isArray(wh[0])) {
|
|
236
|
+
selectors = wh[0];
|
|
237
|
+
} else {
|
|
238
|
+
selectors = wh;
|
|
239
|
+
}
|
|
240
|
+
selectedRows = selectors.map((item, i) => {
|
|
241
|
+
let r = this.row(item);
|
|
242
|
+
if (__privateGet(this, _rowNames2) && r) {
|
|
243
|
+
selectedRowNames == null ? void 0 : selectedRowNames.push(__privateGet(this, _rowNames2)[i]);
|
|
244
|
+
}
|
|
245
|
+
return r;
|
|
246
|
+
}).filter((row) => row !== void 0);
|
|
247
|
+
}
|
|
248
|
+
return selectedRows;
|
|
249
|
+
}
|
|
250
|
+
col(wh) {
|
|
251
|
+
if (typeof wh === "number") {
|
|
252
|
+
return this[wh];
|
|
253
|
+
} else if (typeof wh === "string") {
|
|
254
|
+
return this.getColumnByName(wh);
|
|
255
|
+
}
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
cols(...wh) {
|
|
259
|
+
let selectors, selectedCols, selectedNames = [];
|
|
260
|
+
if (wh.length === 0) {
|
|
261
|
+
selectedCols = this.slice();
|
|
262
|
+
selectedNames = __privateGet(this, _names2);
|
|
263
|
+
} else {
|
|
264
|
+
if (wh.length === 1 && Array.isArray(wh[0])) {
|
|
265
|
+
selectors = wh[0];
|
|
266
|
+
} else {
|
|
267
|
+
selectors = wh;
|
|
268
|
+
}
|
|
269
|
+
selectedCols = selectors.map((item) => this.col(item)).filter((col) => col !== void 0);
|
|
270
|
+
selectedNames = selectedCols.map((col) => col.name);
|
|
271
|
+
}
|
|
272
|
+
return new _DataFrame(selectedNames, selectedCols, __privateGet(this, _rowNames2));
|
|
273
|
+
}
|
|
274
|
+
subset(rows, cols) {
|
|
275
|
+
const selectedCols = cols && cols.length > 0 ? this.cols(...cols) : this.slice();
|
|
276
|
+
return new _DataFrame(
|
|
277
|
+
selectedCols.map((col) => col.name),
|
|
278
|
+
selectedCols.map((col) => col.values),
|
|
279
|
+
__privateGet(this, _rowNames2)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
get names() {
|
|
283
|
+
return __privateGet(this, _names2);
|
|
284
|
+
}
|
|
285
|
+
get rowNames() {
|
|
286
|
+
return __privateGet(this, _rowNames2);
|
|
287
|
+
}
|
|
288
|
+
get nrow() {
|
|
289
|
+
return this[0].length;
|
|
290
|
+
}
|
|
291
|
+
get ncol() {
|
|
292
|
+
return this.length;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
_names2 = new WeakMap();
|
|
296
|
+
_rowNames2 = new WeakMap();
|
|
297
|
+
_classes2 = new WeakMap();
|
|
298
|
+
__name(_DataFrame, "DataFrame");
|
|
299
|
+
let DataFrame = _DataFrame;
|
|
300
|
+
const _Model = class _Model {
|
|
301
|
+
constructor(o = {}) {
|
|
302
|
+
this.unit_id_col = "id";
|
|
303
|
+
this.node_id_col = "id";
|
|
304
|
+
this.group_col = null;
|
|
305
|
+
this.directed = false;
|
|
306
|
+
this._points = o.points ?? new DataFrame();
|
|
307
|
+
this._nodes = o.nodes ?? new DataFrame();
|
|
308
|
+
this._edges = o.edges ?? new DataFrame();
|
|
309
|
+
this._dimensions = o.dimensions ?? [];
|
|
310
|
+
this._confidence = void 0;
|
|
311
|
+
this._outlier = void 0;
|
|
312
|
+
this._groups = void 0;
|
|
313
|
+
}
|
|
314
|
+
get updated() {
|
|
315
|
+
return this._updated;
|
|
316
|
+
}
|
|
317
|
+
set updated(v) {
|
|
318
|
+
this._updated = v;
|
|
319
|
+
}
|
|
320
|
+
get points() {
|
|
321
|
+
return this._points;
|
|
322
|
+
}
|
|
323
|
+
set points(v) {
|
|
324
|
+
this._points = v;
|
|
325
|
+
}
|
|
326
|
+
get nodes() {
|
|
327
|
+
return this._nodes;
|
|
328
|
+
}
|
|
329
|
+
set nodes(v) {
|
|
330
|
+
this._nodes = v;
|
|
331
|
+
}
|
|
332
|
+
get edges() {
|
|
333
|
+
return this._edges;
|
|
334
|
+
}
|
|
335
|
+
set edges(v) {
|
|
336
|
+
this._edges = v;
|
|
337
|
+
}
|
|
338
|
+
get confidence() {
|
|
339
|
+
return this._confidence;
|
|
340
|
+
}
|
|
341
|
+
set confidence(v) {
|
|
342
|
+
this._confidence = v;
|
|
343
|
+
}
|
|
344
|
+
get outlier() {
|
|
345
|
+
return this._outlier;
|
|
346
|
+
}
|
|
347
|
+
set outlier(v) {
|
|
348
|
+
this._outlier = v;
|
|
349
|
+
}
|
|
350
|
+
get groups() {
|
|
351
|
+
return this._groups;
|
|
352
|
+
}
|
|
353
|
+
set groups(v) {
|
|
354
|
+
this._groups = v;
|
|
355
|
+
}
|
|
356
|
+
get dimensions() {
|
|
357
|
+
return this._dimensions;
|
|
358
|
+
}
|
|
359
|
+
set dimensions(v) {
|
|
360
|
+
this._dimensions = v;
|
|
361
|
+
}
|
|
362
|
+
get params() {
|
|
363
|
+
return this._params;
|
|
364
|
+
}
|
|
365
|
+
set params(v) {
|
|
366
|
+
this._params = v;
|
|
367
|
+
}
|
|
368
|
+
point(id) {
|
|
369
|
+
return this._points.row(id);
|
|
370
|
+
}
|
|
371
|
+
setByKey(key, value) {
|
|
372
|
+
this[key] = value;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
__name(_Model, "Model");
|
|
376
|
+
let Model = _Model;
|
|
377
|
+
const DEBUG = globalThis.__QEVIZ_DEBUG__ === true;
|
|
378
|
+
function dbg(...args) {
|
|
379
|
+
if (DEBUG) console.log(...args);
|
|
380
|
+
}
|
|
381
|
+
__name(dbg, "dbg");
|
|
382
|
+
const vizENA = {
|
|
383
|
+
get: /* @__PURE__ */ __name((n) => {
|
|
384
|
+
const elements = [...document.querySelectorAll("qe-graph")];
|
|
385
|
+
if (typeof n === "number" && n >= 0) {
|
|
386
|
+
return elements[n];
|
|
387
|
+
}
|
|
388
|
+
return elements;
|
|
389
|
+
}, "get")
|
|
390
|
+
};
|
|
391
|
+
const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
392
|
+
constructor() {
|
|
393
|
+
super();
|
|
394
|
+
this._isVisible = true;
|
|
395
|
+
this.output = "";
|
|
396
|
+
this._updated = "";
|
|
397
|
+
this._active_points = [];
|
|
398
|
+
this._points = /* @__PURE__ */ new Map();
|
|
399
|
+
this._edges = /* @__PURE__ */ new Map();
|
|
400
|
+
this._labels = /* @__PURE__ */ new Map();
|
|
401
|
+
this._model = new Model();
|
|
402
|
+
const shadow = this.attachShadow({ mode: "open" });
|
|
403
|
+
const style = document.createElement("style");
|
|
404
|
+
style.textContent = _QEVisualElement.styles;
|
|
405
|
+
shadow.appendChild(style);
|
|
406
|
+
this._outputElement = document.createElement("div");
|
|
407
|
+
shadow.appendChild(this._outputElement);
|
|
408
|
+
}
|
|
409
|
+
static get observedAttributes() {
|
|
410
|
+
return ["updated"];
|
|
411
|
+
}
|
|
412
|
+
/** Public accessor so ena-graph (and external code) can read the model. */
|
|
413
|
+
get model() {
|
|
414
|
+
return this._model;
|
|
415
|
+
}
|
|
416
|
+
get updated() {
|
|
417
|
+
return this._updated;
|
|
418
|
+
}
|
|
419
|
+
set updated(val) {
|
|
420
|
+
this._updated = val;
|
|
421
|
+
this.setAttribute("updated", val);
|
|
422
|
+
this.dispatchEvent(new CustomEvent("model-updated", {
|
|
423
|
+
detail: this._model,
|
|
424
|
+
bubbles: true,
|
|
425
|
+
composed: true
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
get points() {
|
|
429
|
+
return this._points;
|
|
430
|
+
}
|
|
431
|
+
set points(pts) {
|
|
432
|
+
this._points.clear();
|
|
433
|
+
if (pts instanceof Map) {
|
|
434
|
+
this._points = pts;
|
|
435
|
+
} else if (Array.isArray(pts)) {
|
|
436
|
+
pts.forEach((p) => this._points.set(p.ID, p));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
get active_points() {
|
|
440
|
+
return Array.from(this._active_points.values());
|
|
441
|
+
}
|
|
442
|
+
get edges() {
|
|
443
|
+
return this._edges;
|
|
444
|
+
}
|
|
445
|
+
set edges(edges) {
|
|
446
|
+
this._edges.clear();
|
|
447
|
+
if (edges instanceof Map) {
|
|
448
|
+
this._edges = edges;
|
|
449
|
+
} else if (Array.isArray(edges)) {
|
|
450
|
+
this._edges = /* @__PURE__ */ new Map();
|
|
451
|
+
edges.forEach((e) => this._edges.set(e.ID, e));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
get labels() {
|
|
455
|
+
return this._labels;
|
|
456
|
+
}
|
|
457
|
+
connectedCallback() {
|
|
458
|
+
dbg("QEVisualElement connected");
|
|
459
|
+
this.render();
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Accept pre-fetched ENA model data and update the visualization.
|
|
463
|
+
* Call this directly when data is available from any source —
|
|
464
|
+
* rENA R package, Python package, or static JSON.
|
|
465
|
+
*/
|
|
466
|
+
setModelData(data) {
|
|
467
|
+
var _a, _b, _c, _d;
|
|
468
|
+
const frameKeys = ["nodes", "edges", "points", "groups", "confidence", "outlier"];
|
|
469
|
+
frameKeys.forEach((name) => {
|
|
470
|
+
var _a2;
|
|
471
|
+
const raw = data[name];
|
|
472
|
+
if (!((_a2 = raw == null ? void 0 : raw.data) == null ? void 0 : _a2.length)) return;
|
|
473
|
+
const rows = raw.data, types = raw.types || {}, colNames = Object.keys(rows[0]), colValues = rows.map((v) => Object.values(v)), classes = colNames.map((col) => types[col] ?? "character");
|
|
474
|
+
let idCol;
|
|
475
|
+
if (name === "nodes") {
|
|
476
|
+
idCol = data.node_id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
477
|
+
} else if (name === "edges" || name === "points") {
|
|
478
|
+
idCol = data.id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
479
|
+
} else {
|
|
480
|
+
idCol = data.group_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
481
|
+
}
|
|
482
|
+
const removalCol = idCol ? colNames.indexOf(idCol) : 0;
|
|
483
|
+
const rowNames = colValues.map((v) => String(v[removalCol]));
|
|
484
|
+
this._model.setByKey(name, new DataFrame(colNames, colValues, rowNames, classes));
|
|
485
|
+
});
|
|
486
|
+
if (data.updated !== void 0) this._model.setByKey("updated", data.updated);
|
|
487
|
+
if (data.directed !== void 0) this._model.directed = data.directed;
|
|
488
|
+
const nodesDF = this._model.nodes;
|
|
489
|
+
this._model.node_id_col = data.node_id_col ?? ((_a = nodesDF == null ? void 0 : nodesDF.names) == null ? void 0 : _a.find((n) => {
|
|
490
|
+
var _a2;
|
|
491
|
+
return ((_a2 = nodesDF.col(n)) == null ? void 0 : _a2.type) === "character";
|
|
492
|
+
})) ?? "id";
|
|
493
|
+
const pointsDF = this._model.points;
|
|
494
|
+
this._model.unit_id_col = data.id_col ?? ((_b = pointsDF == null ? void 0 : pointsDF.names) == null ? void 0 : _b.find((n) => {
|
|
495
|
+
var _a2;
|
|
496
|
+
return ((_a2 = pointsDF.col(n)) == null ? void 0 : _a2.type) === "character";
|
|
497
|
+
})) ?? "id";
|
|
498
|
+
const unitId = this._model.unit_id_col;
|
|
499
|
+
this._model.group_col = data.group_col ?? ((_c = pointsDF == null ? void 0 : pointsDF.names) == null ? void 0 : _c.find((n) => {
|
|
500
|
+
var _a2;
|
|
501
|
+
return n !== unitId && ((_a2 = pointsDF.col(n)) == null ? void 0 : _a2.type) === "character";
|
|
502
|
+
})) ?? null;
|
|
503
|
+
if ((_d = nodesDF == null ? void 0 : nodesDF.names) == null ? void 0 : _d.length) {
|
|
504
|
+
const nodeId = this._model.node_id_col;
|
|
505
|
+
const xColOvr = data.x_col;
|
|
506
|
+
const yColOvr = data.y_col;
|
|
507
|
+
if (xColOvr && yColOvr) {
|
|
508
|
+
this._model.dimensions = [xColOvr, yColOvr];
|
|
509
|
+
} else {
|
|
510
|
+
const numCols = nodesDF.names.filter((n) => {
|
|
511
|
+
if (n === nodeId) return false;
|
|
512
|
+
const col = nodesDF.col(n);
|
|
513
|
+
return col && col.type !== "character" && col.type !== "logical";
|
|
514
|
+
});
|
|
515
|
+
if (numCols.length >= 2) {
|
|
516
|
+
this._model.dimensions = [xColOvr ?? numCols[0], yColOvr ?? numCols[1]];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this.updated = this._model.updated !== void 0 ? String(this._model.updated) : String(Date.now());
|
|
521
|
+
dbg("Model updated via setModelData:", this.updated);
|
|
522
|
+
this.render();
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Base implementation is a no-op. Subclasses (e.g. RQEVisualElement) override
|
|
526
|
+
* this to fetch data from a backend and call setModelData().
|
|
527
|
+
*/
|
|
528
|
+
async calculate() {
|
|
529
|
+
}
|
|
530
|
+
render() {
|
|
531
|
+
var _a;
|
|
532
|
+
if (!this._isVisible) {
|
|
533
|
+
this.classList.add("hidden");
|
|
534
|
+
} else {
|
|
535
|
+
this.classList.remove("hidden");
|
|
536
|
+
}
|
|
537
|
+
if (((_a = this._model) == null ? void 0 : _a.updated) !== void 0 && this._model.updated !== null) {
|
|
538
|
+
this._outputElement.innerHTML = `${this.output}<slot></slot>`;
|
|
539
|
+
} else {
|
|
540
|
+
this._outputElement.innerHTML = "Define the model on the left.";
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
__name(_QEVisualElement, "QEVisualElement");
|
|
545
|
+
_QEVisualElement.tagName = "qe-visual";
|
|
546
|
+
_QEVisualElement.styles = `
|
|
547
|
+
:host { display: inline-block; }
|
|
548
|
+
:host(.hidden) { display: none !important; }
|
|
549
|
+
`;
|
|
550
|
+
let QEVisualElement = _QEVisualElement;
|
|
551
|
+
customElements.define(QEVisualElement.tagName, QEVisualElement);
|
|
552
|
+
const _Color = class _Color {
|
|
553
|
+
constructor(base) {
|
|
554
|
+
this.base = typeof base === "string" ? base : base.base;
|
|
555
|
+
return new Proxy(this, {
|
|
556
|
+
get(target, prop) {
|
|
557
|
+
if (typeof prop === "string" && /^[0-2]$/.test(prop)) {
|
|
558
|
+
const { r, g, b } = _Color.parseColor(target.base);
|
|
559
|
+
return [r, g, b][Number(prop)];
|
|
560
|
+
}
|
|
561
|
+
return target[prop];
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
toString() {
|
|
566
|
+
return this.base;
|
|
567
|
+
}
|
|
568
|
+
setBase(base) {
|
|
569
|
+
this.base = base;
|
|
570
|
+
}
|
|
571
|
+
static parseColor(color) {
|
|
572
|
+
if (color.startsWith("#")) {
|
|
573
|
+
const bigint = parseInt(color.slice(1), 16);
|
|
574
|
+
return {
|
|
575
|
+
r: bigint >> 16 & 255,
|
|
576
|
+
g: bigint >> 8 & 255,
|
|
577
|
+
b: bigint & 255
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
throw new Error("Unsupported color format");
|
|
581
|
+
}
|
|
582
|
+
static rgb(r, g, b) {
|
|
583
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
__name(_Color, "Color");
|
|
587
|
+
let Color = _Color;
|
|
588
|
+
const _WeightedColor = class _WeightedColor extends Color {
|
|
589
|
+
constructor(base, weight) {
|
|
590
|
+
super(base);
|
|
591
|
+
this.weight = weight;
|
|
592
|
+
}
|
|
593
|
+
// Parse hex → HSV (h in [0, 360), s/v in [0, 1]).
|
|
594
|
+
static toHSV(hex) {
|
|
595
|
+
const { r, g, b } = Color.parseColor(hex);
|
|
596
|
+
const rn = r / 255, gn = g / 255, bn = b / 255;
|
|
597
|
+
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
|
598
|
+
const d = max - min;
|
|
599
|
+
const v = max;
|
|
600
|
+
const s = max === 0 ? 0 : d / max;
|
|
601
|
+
let h = 0;
|
|
602
|
+
if (d > 0) {
|
|
603
|
+
switch (max) {
|
|
604
|
+
case rn:
|
|
605
|
+
h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
|
606
|
+
break;
|
|
607
|
+
case gn:
|
|
608
|
+
h = ((bn - rn) / d + 2) / 6;
|
|
609
|
+
break;
|
|
610
|
+
default:
|
|
611
|
+
h = ((rn - gn) / d + 4) / 6;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return { h: h * 360, s, v };
|
|
615
|
+
}
|
|
616
|
+
// Convert HSV + alpha → rgba() CSS string.
|
|
617
|
+
static hsvToRgba(h, s, v, a) {
|
|
618
|
+
const c = v * s;
|
|
619
|
+
const x = c * (1 - Math.abs(h / 60 % 2 - 1));
|
|
620
|
+
const m = v - c;
|
|
621
|
+
let r = 0, g = 0, b = 0;
|
|
622
|
+
if (h < 60) {
|
|
623
|
+
r = c;
|
|
624
|
+
g = x;
|
|
625
|
+
b = 0;
|
|
626
|
+
} else if (h < 120) {
|
|
627
|
+
r = x;
|
|
628
|
+
g = c;
|
|
629
|
+
b = 0;
|
|
630
|
+
} else if (h < 180) {
|
|
631
|
+
r = 0;
|
|
632
|
+
g = c;
|
|
633
|
+
b = x;
|
|
634
|
+
} else if (h < 240) {
|
|
635
|
+
r = 0;
|
|
636
|
+
g = x;
|
|
637
|
+
b = c;
|
|
638
|
+
} else if (h < 300) {
|
|
639
|
+
r = x;
|
|
640
|
+
g = 0;
|
|
641
|
+
b = c;
|
|
642
|
+
} else {
|
|
643
|
+
r = c;
|
|
644
|
+
g = 0;
|
|
645
|
+
b = x;
|
|
646
|
+
}
|
|
647
|
+
return `rgba(${Math.round((r + m) * 255)},${Math.round((g + m) * 255)},${Math.round((b + m) * 255)},${a.toFixed(4)})`;
|
|
648
|
+
}
|
|
649
|
+
// Returns rgba(...) matching rENA's edge-color convention (ena.plot.network):
|
|
650
|
+
// • hue and value (brightness) are preserved from the base color
|
|
651
|
+
// • HSV saturation is set to |weight| directly (0 = grey, 1 = fully vivid)
|
|
652
|
+
// • alpha is also set to |weight|
|
|
653
|
+
//
|
|
654
|
+
// rENA converts the input color to HSV, replaces the S component with the
|
|
655
|
+
// normalized edge weight, and additionally sets plotly element opacity to the
|
|
656
|
+
// same value. Working in HSV (not HSL) is essential: palette colors like
|
|
657
|
+
// #EE6677 have HSL lightness ≈ 67%, so 100% HSL saturation still looks
|
|
658
|
+
// washed-out, whereas HSV value ≈ 0.93 gives the expected vivid output.
|
|
659
|
+
toString() {
|
|
660
|
+
const { h, v } = _WeightedColor.toHSV(this.base);
|
|
661
|
+
const w = Math.min(1, Math.abs(this.weight));
|
|
662
|
+
return _WeightedColor.hsvToRgba(h, w, v, w);
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
__name(_WeightedColor, "WeightedColor");
|
|
666
|
+
let WeightedColor = _WeightedColor;
|
|
667
|
+
const _Transform = class _Transform {
|
|
668
|
+
constructor(vals = null) {
|
|
669
|
+
this._values = [1, 0, 0, 1, 0, 0];
|
|
670
|
+
this._keys = ["a", "b", "c", "d", "e", "f"];
|
|
671
|
+
if (vals) {
|
|
672
|
+
this.values = vals;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
get values() {
|
|
676
|
+
return this._values;
|
|
677
|
+
}
|
|
678
|
+
set values(vals) {
|
|
679
|
+
if (vals && vals.length === 6) {
|
|
680
|
+
this._values = vals;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
get keys() {
|
|
684
|
+
return this._keys;
|
|
685
|
+
}
|
|
686
|
+
set keys(vals) {
|
|
687
|
+
if (vals && vals.length === 6) {
|
|
688
|
+
this._keys = vals;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
toString() {
|
|
692
|
+
let matrix_str = this.keys.map((k, i) => this.values[i]).join(" ");
|
|
693
|
+
return `matrix(${matrix_str})`;
|
|
694
|
+
}
|
|
695
|
+
toMatrix() {
|
|
696
|
+
return Object.fromEntries(this._keys.map((k, i) => [k, this._values[i]]));
|
|
697
|
+
}
|
|
698
|
+
toArray() {
|
|
699
|
+
return this._values;
|
|
700
|
+
}
|
|
701
|
+
set(wh, val) {
|
|
702
|
+
if (val !== 75e-4 && val !== 0) {
|
|
703
|
+
this._values[this._keys.findIndex((k) => k === wh)] = val;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
get(wh) {
|
|
707
|
+
return this._values[this._keys.findIndex((k) => k === wh)];
|
|
708
|
+
}
|
|
709
|
+
scale(a, d = a) {
|
|
710
|
+
this.a = a;
|
|
711
|
+
this.d = d;
|
|
712
|
+
}
|
|
713
|
+
zoom(z) {
|
|
714
|
+
this.a = z;
|
|
715
|
+
this.d = z;
|
|
716
|
+
}
|
|
717
|
+
pan(x, y) {
|
|
718
|
+
this.e = this.e + x;
|
|
719
|
+
this.f = this.f + y;
|
|
720
|
+
}
|
|
721
|
+
set a(x) {
|
|
722
|
+
this.set("a", x);
|
|
723
|
+
}
|
|
724
|
+
set b(x) {
|
|
725
|
+
this.set("b", x);
|
|
726
|
+
}
|
|
727
|
+
set c(x) {
|
|
728
|
+
this.set("c", x);
|
|
729
|
+
}
|
|
730
|
+
set d(x) {
|
|
731
|
+
this.set("d", x);
|
|
732
|
+
}
|
|
733
|
+
set e(x) {
|
|
734
|
+
this.set("e", x);
|
|
735
|
+
}
|
|
736
|
+
set f(x) {
|
|
737
|
+
this.set("f", x);
|
|
738
|
+
}
|
|
739
|
+
get a() {
|
|
740
|
+
return this.get("a");
|
|
741
|
+
}
|
|
742
|
+
get b() {
|
|
743
|
+
return this.get("b");
|
|
744
|
+
}
|
|
745
|
+
get c() {
|
|
746
|
+
return this.get("c");
|
|
747
|
+
}
|
|
748
|
+
get d() {
|
|
749
|
+
return this.get("d");
|
|
750
|
+
}
|
|
751
|
+
get e() {
|
|
752
|
+
return this.get("e");
|
|
753
|
+
}
|
|
754
|
+
get f() {
|
|
755
|
+
return this.get("f");
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
__name(_Transform, "Transform");
|
|
759
|
+
let Transform = _Transform;
|
|
760
|
+
var Tools;
|
|
761
|
+
((Tools2) => {
|
|
762
|
+
((Matrix2) => {
|
|
763
|
+
function matrixString(matrix) {
|
|
764
|
+
const matrixStr = ["a", "b", "c", "d", "e", "f"].map((l) => matrix[l]).join(" ");
|
|
765
|
+
return `matrix(${matrixStr})`;
|
|
766
|
+
}
|
|
767
|
+
__name(matrixString, "matrixString");
|
|
768
|
+
Matrix2.matrixString = matrixString;
|
|
769
|
+
})(Tools2.Matrix || (Tools2.Matrix = {}));
|
|
770
|
+
((Geometry2) => {
|
|
771
|
+
((Point2) => {
|
|
772
|
+
function translate(point, angle, distance) {
|
|
773
|
+
const r = Angle.toRad(angle);
|
|
774
|
+
return [point[0] + distance * Math.cos(r), point[1] + distance * Math.sin(r)];
|
|
775
|
+
}
|
|
776
|
+
__name(translate, "translate");
|
|
777
|
+
Point2.translate = translate;
|
|
778
|
+
function rotate(point, angle, origin) {
|
|
779
|
+
const r = Angle.toRad(angle);
|
|
780
|
+
function rotatePoint(p, a) {
|
|
781
|
+
return [
|
|
782
|
+
p[0] * Math.cos(a) - p[1] * Math.sin(a),
|
|
783
|
+
p[0] * Math.sin(a) + p[1] * Math.cos(a)
|
|
784
|
+
];
|
|
785
|
+
}
|
|
786
|
+
__name(rotatePoint, "rotatePoint");
|
|
787
|
+
if (!origin || origin[0] === 0 && origin[1] === 0) {
|
|
788
|
+
return rotatePoint(point, r);
|
|
789
|
+
} else {
|
|
790
|
+
const p0 = point.map((c, i) => c - origin[i]);
|
|
791
|
+
const rotated = rotatePoint(p0, r);
|
|
792
|
+
return rotated.map((c, i) => c + origin[i]);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
__name(rotate, "rotate");
|
|
796
|
+
Point2.rotate = rotate;
|
|
797
|
+
})(Geometry2.Point || (Geometry2.Point = {}));
|
|
798
|
+
((Line2) => {
|
|
799
|
+
function slope(p1, p2) {
|
|
800
|
+
return (p2[1] - p1[1]) / (p2[0] - p1[0]);
|
|
801
|
+
}
|
|
802
|
+
__name(slope, "slope");
|
|
803
|
+
Line2.slope = slope;
|
|
804
|
+
function intercept(p1, p2) {
|
|
805
|
+
const m = slope(p1, p2);
|
|
806
|
+
return p1[1] - m * p1[0];
|
|
807
|
+
}
|
|
808
|
+
__name(intercept, "intercept");
|
|
809
|
+
Line2.intercept = intercept;
|
|
810
|
+
function slopeIntercept(p1, p2) {
|
|
811
|
+
return {
|
|
812
|
+
slope: slope(p1, p2),
|
|
813
|
+
intercept: intercept(p1, p2)
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
__name(slopeIntercept, "slopeIntercept");
|
|
817
|
+
Line2.slopeIntercept = slopeIntercept;
|
|
818
|
+
function point_along(line, distance = 0) {
|
|
819
|
+
let len = Tools2.Geometry.Line.length(line[0], line[1]), ratio = distance / len, Xt = (1 - ratio) * line[0][0] + ratio * line[1][0], Yt = (1 - ratio) * line[0][1] + ratio * line[1][1];
|
|
820
|
+
return [Xt, Yt];
|
|
821
|
+
}
|
|
822
|
+
__name(point_along, "point_along");
|
|
823
|
+
Line2.point_along = point_along;
|
|
824
|
+
function midpoint(p1, p2, w = 0.5) {
|
|
825
|
+
if (Array.isArray(p1[0])) {
|
|
826
|
+
p2 = p1[1];
|
|
827
|
+
p1 = p1[0];
|
|
828
|
+
}
|
|
829
|
+
return [0, 1].map((i) => (parseFloat(p1[i]) + parseFloat(p2[i])) * w);
|
|
830
|
+
}
|
|
831
|
+
__name(midpoint, "midpoint");
|
|
832
|
+
Line2.midpoint = midpoint;
|
|
833
|
+
function length(p1, p2) {
|
|
834
|
+
if (Array.isArray(p1[0])) {
|
|
835
|
+
p2 = p1[1];
|
|
836
|
+
p1 = p1[0];
|
|
837
|
+
}
|
|
838
|
+
return Math.sqrt(
|
|
839
|
+
[0, 1].map((i) => Math.pow(p2[i] - p1[i], 2)).reduce((a, b) => a + b, 0)
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
__name(length, "length");
|
|
843
|
+
Line2.length = length;
|
|
844
|
+
function rotate(line, by = [0, 0]) {
|
|
845
|
+
const translated = [
|
|
846
|
+
line[1][0] - by[0],
|
|
847
|
+
line[1][1] - by[1]
|
|
848
|
+
];
|
|
849
|
+
const rotated = [
|
|
850
|
+
-translated[1],
|
|
851
|
+
translated[0]
|
|
852
|
+
];
|
|
853
|
+
const rotated_reflection = [
|
|
854
|
+
-rotated[0],
|
|
855
|
+
-rotated[1]
|
|
856
|
+
];
|
|
857
|
+
const untranslated = [
|
|
858
|
+
rotated[0] + by[0],
|
|
859
|
+
rotated[1] + by[1]
|
|
860
|
+
];
|
|
861
|
+
const untranslated2 = [
|
|
862
|
+
rotated_reflection[0] + by[0],
|
|
863
|
+
rotated_reflection[1] + by[1]
|
|
864
|
+
];
|
|
865
|
+
return [untranslated, untranslated2];
|
|
866
|
+
}
|
|
867
|
+
__name(rotate, "rotate");
|
|
868
|
+
Line2.rotate = rotate;
|
|
869
|
+
function rotate2(line, fixed, angle = 90) {
|
|
870
|
+
const m = Tools2.Geometry.Line.slope(line[0], line[1]);
|
|
871
|
+
const n = Tools2.Geometry.Line.intercept(line[0], line[1]);
|
|
872
|
+
const P = [0, n];
|
|
873
|
+
const Q = [1, m + n];
|
|
874
|
+
line = [P, Q];
|
|
875
|
+
const xx = [line[0][0], line[1][0]];
|
|
876
|
+
const yy = [line[0][1], line[1][1]];
|
|
877
|
+
const x_fixed = fixed[0];
|
|
878
|
+
const y_fixed = fixed[1];
|
|
879
|
+
const angle_rad = -2 * Math.PI * angle / 360;
|
|
880
|
+
const xx_new = xx.map(
|
|
881
|
+
(x, i) => (x - x_fixed) * Math.cos(angle_rad) - (yy[i] - y_fixed) * Math.sin(angle_rad) + x_fixed
|
|
882
|
+
);
|
|
883
|
+
const yy_new = yy.map(
|
|
884
|
+
(y, i) => (xx[i] - x_fixed) * Math.sin(angle_rad) - (y - y_fixed) * Math.cos(angle_rad) + y_fixed
|
|
885
|
+
);
|
|
886
|
+
const object_new = [
|
|
887
|
+
[xx_new[0], yy_new[0]],
|
|
888
|
+
[xx_new[1], yy_new[1]]
|
|
889
|
+
];
|
|
890
|
+
return object_new;
|
|
891
|
+
}
|
|
892
|
+
__name(rotate2, "rotate2");
|
|
893
|
+
Line2.rotate2 = rotate2;
|
|
894
|
+
function intersection(lineA, lineB) {
|
|
895
|
+
const [[x1, y1], [x2, y2]] = lineA, [[x3, y3], [x4, y4]] = lineB, D = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4), num1 = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), num2 = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
|
|
896
|
+
return [num1 / D, num2 / D];
|
|
897
|
+
}
|
|
898
|
+
__name(intersection, "intersection");
|
|
899
|
+
Line2.intersection = intersection;
|
|
900
|
+
function intersect_circle(line, circle_xy, circle_r) {
|
|
901
|
+
let { slope: m, intercept: n } = Tools2.Geometry.Line.slopeIntercept(line[0], line[1]);
|
|
902
|
+
let a = 1 + Math.pow(m, 2), b = 2 * m * n - 2 * circle_xy[1] * m - 2 * circle_xy[0], c = Math.pow(circle_xy[0], 2) + Math.pow(n, 2) + Math.pow(circle_xy[1], 2) - 2 * circle_xy[1] * n - Math.pow(circle_r, 2), int = [[Infinity, Infinity], [Infinity, Infinity]];
|
|
903
|
+
if (Math.sqrt(Math.pow(b, 2) - 4 * a * c) >= 0) {
|
|
904
|
+
let x1 = (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a), x2 = (-b - Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / (2 * a), y1 = m * x1 + n, y2 = m * x2 + n;
|
|
905
|
+
int = [[x1, y1], [x2, y2]];
|
|
906
|
+
} else {
|
|
907
|
+
console.error("The line and the circumference do not intersect");
|
|
908
|
+
}
|
|
909
|
+
return int;
|
|
910
|
+
}
|
|
911
|
+
__name(intersect_circle, "intersect_circle");
|
|
912
|
+
Line2.intersect_circle = intersect_circle;
|
|
913
|
+
function interpolate(line) {
|
|
914
|
+
return (t) => t === 0 ? line[0] : t === 1 ? line[1] : Tools2.Geometry.Point.translate(line[0], Tools2.Geometry.Angle.line(line), Tools2.Geometry.Line.length(line) * t);
|
|
915
|
+
}
|
|
916
|
+
__name(interpolate, "interpolate");
|
|
917
|
+
Line2.interpolate = interpolate;
|
|
918
|
+
})(Geometry2.Line || (Geometry2.Line = {}));
|
|
919
|
+
let Angle;
|
|
920
|
+
((Angle2) => {
|
|
921
|
+
function toRad(angle) {
|
|
922
|
+
return angle / 180 * Math.PI;
|
|
923
|
+
}
|
|
924
|
+
__name(toRad, "toRad");
|
|
925
|
+
Angle2.toRad = toRad;
|
|
926
|
+
function toDeg(angle) {
|
|
927
|
+
return angle * 180 / Math.PI;
|
|
928
|
+
}
|
|
929
|
+
__name(toDeg, "toDeg");
|
|
930
|
+
Angle2.toDeg = toDeg;
|
|
931
|
+
function line(line2) {
|
|
932
|
+
if (line2.length !== 2 || !Array.isArray(line2[0]) || !Array.isArray(line2[1]) || line2[0].length !== 2 || line2[1].length !== 2) {
|
|
933
|
+
throw new Error("Invalid line format. Expected [[x1, y1], [x2, y2]].");
|
|
934
|
+
}
|
|
935
|
+
return Tools2.Geometry.Angle.toDeg(
|
|
936
|
+
Math.atan2(line2[1][1] - line2[0][1], line2[1][0] - line2[0][0])
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
__name(line, "line");
|
|
940
|
+
Angle2.line = line;
|
|
941
|
+
})(Angle = Geometry2.Angle || (Geometry2.Angle = {}));
|
|
942
|
+
})(Tools2.Geometry || (Tools2.Geometry = {}));
|
|
943
|
+
((ArrayTools2) => {
|
|
944
|
+
function unique(arr) {
|
|
945
|
+
return [...new Set(arr)];
|
|
946
|
+
}
|
|
947
|
+
__name(unique, "unique");
|
|
948
|
+
ArrayTools2.unique = unique;
|
|
949
|
+
function divide(a, b) {
|
|
950
|
+
if (Array.isArray(a)) {
|
|
951
|
+
if (Array.isArray(b)) {
|
|
952
|
+
if (a.length !== b.length) throw new Error("Arrays must be of equal length");
|
|
953
|
+
return a.map((val, i) => val / b[i]);
|
|
954
|
+
}
|
|
955
|
+
return a.map((val) => val / b);
|
|
956
|
+
}
|
|
957
|
+
return a / b;
|
|
958
|
+
}
|
|
959
|
+
__name(divide, "divide");
|
|
960
|
+
ArrayTools2.divide = divide;
|
|
961
|
+
function isArray(obj) {
|
|
962
|
+
return typeof Array.isArray === "function" ? Array.isArray(obj) : obj && typeof obj === "object" && obj.constructor.name === "Array";
|
|
963
|
+
}
|
|
964
|
+
__name(isArray, "isArray");
|
|
965
|
+
ArrayTools2.isArray = isArray;
|
|
966
|
+
})(Tools2.ArrayTools || (Tools2.ArrayTools = {}));
|
|
967
|
+
((Mathematics2) => {
|
|
968
|
+
function sum(arr) {
|
|
969
|
+
return arr.reduce((a, b) => a + b, 0);
|
|
970
|
+
}
|
|
971
|
+
__name(sum, "sum");
|
|
972
|
+
Mathematics2.sum = sum;
|
|
973
|
+
function mean(arr) {
|
|
974
|
+
return sum(arr) / arr.length;
|
|
975
|
+
}
|
|
976
|
+
__name(mean, "mean");
|
|
977
|
+
Mathematics2.mean = mean;
|
|
978
|
+
function round(value, decimals) {
|
|
979
|
+
const factor = Math.pow(10, decimals);
|
|
980
|
+
return Math.round(value * factor) / factor;
|
|
981
|
+
}
|
|
982
|
+
__name(round, "round");
|
|
983
|
+
Mathematics2.round = round;
|
|
984
|
+
})(Tools2.Mathematics || (Tools2.Mathematics = {}));
|
|
985
|
+
((Events2) => {
|
|
986
|
+
function createEvent(name, element, data) {
|
|
987
|
+
return new CustomEvent(name, {
|
|
988
|
+
bubbles: true,
|
|
989
|
+
cancelable: true,
|
|
990
|
+
detail: {
|
|
991
|
+
element,
|
|
992
|
+
data: data || (element ? element.__data__ : void 0)
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
__name(createEvent, "createEvent");
|
|
997
|
+
Events2.createEvent = createEvent;
|
|
998
|
+
})(Tools2.Events || (Tools2.Events = {}));
|
|
999
|
+
((Hash2) => {
|
|
1000
|
+
function parts() {
|
|
1001
|
+
return location.hash.replace(/#/, "").split(/\//).filter((a) => a).map((a) => a.split(/:/));
|
|
1002
|
+
}
|
|
1003
|
+
__name(parts, "parts");
|
|
1004
|
+
Hash2.parts = parts;
|
|
1005
|
+
function exists(key) {
|
|
1006
|
+
return parts().flat().findIndex((a) => a === key) >= 0;
|
|
1007
|
+
}
|
|
1008
|
+
__name(exists, "exists");
|
|
1009
|
+
Hash2.exists = exists;
|
|
1010
|
+
})(Tools2.Hash || (Tools2.Hash = {}));
|
|
1011
|
+
((Objects2) => {
|
|
1012
|
+
function proxy(obj, handler2, deep = false) {
|
|
1013
|
+
if (!deep) {
|
|
1014
|
+
return new Proxy(obj, handler2);
|
|
1015
|
+
} else {
|
|
1016
|
+
if (typeof obj !== "object" || obj === null) {
|
|
1017
|
+
return obj;
|
|
1018
|
+
}
|
|
1019
|
+
for (const key in obj) {
|
|
1020
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1021
|
+
obj[key] = proxy(obj[key], handler2, deep);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return new Proxy(obj, handler2);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
__name(proxy, "proxy");
|
|
1028
|
+
Objects2.proxy = proxy;
|
|
1029
|
+
Objects2.handler = {
|
|
1030
|
+
set(target, property, value) {
|
|
1031
|
+
target[property] = value;
|
|
1032
|
+
window.dispatchEvent(
|
|
1033
|
+
Tools2.Events.createEvent("settingUpdate", window, { target, property, value })
|
|
1034
|
+
);
|
|
1035
|
+
return true;
|
|
1036
|
+
},
|
|
1037
|
+
get(target, property) {
|
|
1038
|
+
return target[property];
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
function get(obj, key) {
|
|
1042
|
+
const parts = key.split(".");
|
|
1043
|
+
let current = obj;
|
|
1044
|
+
for (const part of parts) {
|
|
1045
|
+
if (current && typeof current === "object" && Object.prototype.hasOwnProperty.call(current, part)) {
|
|
1046
|
+
current = current[part];
|
|
1047
|
+
} else {
|
|
1048
|
+
return void 0;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return current;
|
|
1052
|
+
}
|
|
1053
|
+
__name(get, "get");
|
|
1054
|
+
Objects2.get = get;
|
|
1055
|
+
function set(obj, key, value) {
|
|
1056
|
+
const parts = key.split(".");
|
|
1057
|
+
let current = obj;
|
|
1058
|
+
let i;
|
|
1059
|
+
for (i = 0; i < parts.length - 1; i++) {
|
|
1060
|
+
const part = parts[i];
|
|
1061
|
+
if (current && typeof current === "object") {
|
|
1062
|
+
if (!Object.prototype.hasOwnProperty.call(current, part)) {
|
|
1063
|
+
current[part] = {};
|
|
1064
|
+
}
|
|
1065
|
+
current = current[part];
|
|
1066
|
+
} else {
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
if (current && typeof current === "object") {
|
|
1071
|
+
current[parts[i]] = value;
|
|
1072
|
+
return true;
|
|
1073
|
+
} else {
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
__name(set, "set");
|
|
1078
|
+
Objects2.set = set;
|
|
1079
|
+
})(Tools2.Objects || (Tools2.Objects = {}));
|
|
1080
|
+
((URL2) => {
|
|
1081
|
+
function parse(location2 = window.location) {
|
|
1082
|
+
return Object.fromEntries(new URLSearchParams(location2.search));
|
|
1083
|
+
}
|
|
1084
|
+
__name(parse, "parse");
|
|
1085
|
+
URL2.parse = parse;
|
|
1086
|
+
((Query2) => {
|
|
1087
|
+
function parse2(location2 = window.location.search) {
|
|
1088
|
+
return Object.fromEntries(
|
|
1089
|
+
location2.replace(/^\?/, "").split(/&/).map((i) => i.split(/=/))
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
__name(parse2, "parse2");
|
|
1093
|
+
Query2.parse = parse2;
|
|
1094
|
+
function keys(location2 = window.location.search) {
|
|
1095
|
+
return location2.replace(/^\?/, "").split(/&/).map((i) => i.split(/=/)[0]);
|
|
1096
|
+
}
|
|
1097
|
+
__name(keys, "keys");
|
|
1098
|
+
Query2.keys = keys;
|
|
1099
|
+
function has(key, location2 = window.location.search) {
|
|
1100
|
+
return keys(location2).findIndex((i) => i === key) >= 0;
|
|
1101
|
+
}
|
|
1102
|
+
__name(has, "has");
|
|
1103
|
+
Query2.has = has;
|
|
1104
|
+
})(URL2.Query || (URL2.Query = {}));
|
|
1105
|
+
})(Tools2.URL || (Tools2.URL = {}));
|
|
1106
|
+
})(Tools || (Tools = {}));
|
|
1107
|
+
const _EventBus = class _EventBus {
|
|
1108
|
+
constructor() {
|
|
1109
|
+
this._state = /* @__PURE__ */ new Map();
|
|
1110
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
1111
|
+
}
|
|
1112
|
+
get state() {
|
|
1113
|
+
return this._state;
|
|
1114
|
+
}
|
|
1115
|
+
hasVariable(key) {
|
|
1116
|
+
return this._state.has(key);
|
|
1117
|
+
}
|
|
1118
|
+
getVariable(key) {
|
|
1119
|
+
return this._state.get(key);
|
|
1120
|
+
}
|
|
1121
|
+
setVariable(key, value, options) {
|
|
1122
|
+
const oldValue = this._state.get(key);
|
|
1123
|
+
this._state.set(key, value);
|
|
1124
|
+
if (!(options == null ? void 0 : options.lazy)) {
|
|
1125
|
+
this.notifyListeners(key, value, oldValue);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
addListener(key, type, listener) {
|
|
1129
|
+
if (!this.listeners.has(key)) this.listeners.set(key, /* @__PURE__ */ new Map());
|
|
1130
|
+
const typeMap = this.listeners.get(key);
|
|
1131
|
+
if (!typeMap.has(type)) typeMap.set(type, /* @__PURE__ */ new Set());
|
|
1132
|
+
typeMap.get(type).add(listener);
|
|
1133
|
+
}
|
|
1134
|
+
removeListener(key, type, listener) {
|
|
1135
|
+
var _a, _b;
|
|
1136
|
+
(_b = (_a = this.listeners.get(key)) == null ? void 0 : _a.get(type)) == null ? void 0 : _b.delete(listener);
|
|
1137
|
+
}
|
|
1138
|
+
getListeners(key, type) {
|
|
1139
|
+
var _a;
|
|
1140
|
+
return Array.from(((_a = this.listeners.get(key)) == null ? void 0 : _a.get(type)) ?? []);
|
|
1141
|
+
}
|
|
1142
|
+
notifyListeners(key, newValue, oldValue) {
|
|
1143
|
+
const typeMap = this.listeners.get(key);
|
|
1144
|
+
if (!typeMap) return;
|
|
1145
|
+
for (const set of typeMap.values()) {
|
|
1146
|
+
set.forEach((listener) => {
|
|
1147
|
+
if (typeof listener === "function") {
|
|
1148
|
+
listener({ key, newValue, oldValue });
|
|
1149
|
+
} else if (typeof listener.calculate === "function") {
|
|
1150
|
+
listener.calculate.call(listener, { key, newValue, oldValue });
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
__name(_EventBus, "EventBus");
|
|
1157
|
+
let EventBus = _EventBus;
|
|
1158
|
+
const { ArrayTools: ArrayTools$1 } = Tools;
|
|
1159
|
+
const _LayerArray = class _LayerArray extends Array {
|
|
1160
|
+
constructor(...args) {
|
|
1161
|
+
super(...args);
|
|
1162
|
+
}
|
|
1163
|
+
search(key, name) {
|
|
1164
|
+
let found;
|
|
1165
|
+
function fn(a) {
|
|
1166
|
+
return a.name === name ? a : void 0;
|
|
1167
|
+
}
|
|
1168
|
+
__name(fn, "fn");
|
|
1169
|
+
function recurse(obj, i, parent = null) {
|
|
1170
|
+
if (ArrayTools$1.isArray(obj)) {
|
|
1171
|
+
for (const [i2, o] of obj.entries()) {
|
|
1172
|
+
found = recurse(o, i2, parent);
|
|
1173
|
+
if (found) {
|
|
1174
|
+
return found;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
} else {
|
|
1178
|
+
found = fn(obj);
|
|
1179
|
+
if (!found) {
|
|
1180
|
+
if (obj && ArrayTools$1.isArray(obj[key])) {
|
|
1181
|
+
for (const [i2, o] of obj[key].entries()) {
|
|
1182
|
+
found = recurse(o, i2, obj);
|
|
1183
|
+
if (found) {
|
|
1184
|
+
return found;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
} else {
|
|
1189
|
+
return found;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return found;
|
|
1193
|
+
}
|
|
1194
|
+
__name(recurse, "recurse");
|
|
1195
|
+
return recurse(this);
|
|
1196
|
+
}
|
|
1197
|
+
flatten() {
|
|
1198
|
+
return this.reduce((a, l) => {
|
|
1199
|
+
a.push(l);
|
|
1200
|
+
if (l.layers && Array.isArray(l.layers)) {
|
|
1201
|
+
a.push(...l.layers);
|
|
1202
|
+
}
|
|
1203
|
+
return a;
|
|
1204
|
+
}, []);
|
|
1205
|
+
}
|
|
1206
|
+
recurse(key, callback, parent = null, depth = 0) {
|
|
1207
|
+
let results = new _LayerArray();
|
|
1208
|
+
for (const layer of this) {
|
|
1209
|
+
let result = callback(layer, parent, depth);
|
|
1210
|
+
if (layer[key] && Array.isArray(layer[key])) {
|
|
1211
|
+
let r = new _LayerArray(...layer[key]).recurse(key, callback, layer, depth + 1);
|
|
1212
|
+
if (r !== void 0) {
|
|
1213
|
+
results.push(...r);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
results.push(result);
|
|
1217
|
+
}
|
|
1218
|
+
console.log("Recurse results: ", results);
|
|
1219
|
+
return results;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
__name(_LayerArray, "LayerArray");
|
|
1223
|
+
let LayerArray = _LayerArray;
|
|
1224
|
+
const _Layer = class _Layer {
|
|
1225
|
+
constructor(ID, o, parent) {
|
|
1226
|
+
this.__state__ = new EventBus();
|
|
1227
|
+
this.scale_x = 1;
|
|
1228
|
+
this.scale_y = 1;
|
|
1229
|
+
this.scale_objects = true;
|
|
1230
|
+
this.layers = new LayerArray();
|
|
1231
|
+
this.objects = /* @__PURE__ */ new Map();
|
|
1232
|
+
this.visible = true;
|
|
1233
|
+
this.ID = ID;
|
|
1234
|
+
this.name = o.name || `layer-${ID}`;
|
|
1235
|
+
this.tabindex = o.tabindex;
|
|
1236
|
+
this.parent = parent;
|
|
1237
|
+
this._transform = new Transform();
|
|
1238
|
+
parent == null ? void 0 : parent.__state__.addListener("graph_updated", "live", ({ key, newValue, oldValue }) => {
|
|
1239
|
+
});
|
|
1240
|
+
parent == null ? void 0 : parent.__state__.addListener(`graph_updated_${this.name}`, "live", ({ key, newValue, oldValue }) => {
|
|
1241
|
+
this.objects = newValue;
|
|
1242
|
+
this.__state__.setVariable(`layer_updated`, this);
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
get transform() {
|
|
1246
|
+
return this._transform;
|
|
1247
|
+
}
|
|
1248
|
+
set transform(t) {
|
|
1249
|
+
this._transform = t;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
__name(_Layer, "Layer");
|
|
1253
|
+
let Layer = _Layer;
|
|
1254
|
+
const _DimensionVector = class _DimensionVector extends Array {
|
|
1255
|
+
constructor(...args) {
|
|
1256
|
+
super(arguments.length);
|
|
1257
|
+
__privateAdd(this, _flipped, false);
|
|
1258
|
+
__privateAdd(this, _names3);
|
|
1259
|
+
for (let i = 0; i < args.length; i++) {
|
|
1260
|
+
this[i] = args[i];
|
|
1261
|
+
}
|
|
1262
|
+
__privateSet(this, _flipped, false);
|
|
1263
|
+
}
|
|
1264
|
+
static fromArray(arr) {
|
|
1265
|
+
const vec = new _DimensionVector();
|
|
1266
|
+
arr.forEach((v, i) => {
|
|
1267
|
+
vec.push(v);
|
|
1268
|
+
});
|
|
1269
|
+
__privateSet(vec, _names3, arr.names);
|
|
1270
|
+
return vec;
|
|
1271
|
+
}
|
|
1272
|
+
flip() {
|
|
1273
|
+
__privateSet(this, _flipped, !__privateGet(this, _flipped));
|
|
1274
|
+
for (let i = 0; i < this.length; i++) {
|
|
1275
|
+
this[i] = -this[i];
|
|
1276
|
+
}
|
|
1277
|
+
return this;
|
|
1278
|
+
}
|
|
1279
|
+
get(wh) {
|
|
1280
|
+
if (typeof wh === "number") {
|
|
1281
|
+
return this[wh];
|
|
1282
|
+
} else if (__privateGet(this, _names3) && __privateGet(this, _names3).includes(wh)) {
|
|
1283
|
+
return this[__privateGet(this, _names3).indexOf(wh)];
|
|
1284
|
+
} else {
|
|
1285
|
+
return void 0;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
get flipped() {
|
|
1289
|
+
return __privateGet(this, _flipped);
|
|
1290
|
+
}
|
|
1291
|
+
get names() {
|
|
1292
|
+
if (__privateGet(this, _names3)) {
|
|
1293
|
+
return __privateGet(this, _names3);
|
|
1294
|
+
} else {
|
|
1295
|
+
return this.map((_, i) => `V${i + 1}`);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
_flipped = new WeakMap();
|
|
1300
|
+
_names3 = new WeakMap();
|
|
1301
|
+
__name(_DimensionVector, "DimensionVector");
|
|
1302
|
+
let DimensionVector = _DimensionVector;
|
|
1303
|
+
const _PlottedObject = class _PlottedObject {
|
|
1304
|
+
constructor(dimensions, o) {
|
|
1305
|
+
this.scale_x = 1;
|
|
1306
|
+
this.scale_y = 1;
|
|
1307
|
+
this.scale_size = 0.05;
|
|
1308
|
+
this.active = false;
|
|
1309
|
+
this.visible = false;
|
|
1310
|
+
this.highlighting = false;
|
|
1311
|
+
this.meta = {};
|
|
1312
|
+
this._x = 0;
|
|
1313
|
+
this._y = 0;
|
|
1314
|
+
this._highlight_default = new Color("#000000");
|
|
1315
|
+
this._size = 0;
|
|
1316
|
+
this.ID = o.ID;
|
|
1317
|
+
this._transform = new Transform();
|
|
1318
|
+
if (dimensions instanceof DimensionVector) {
|
|
1319
|
+
this._dimensions = dimensions;
|
|
1320
|
+
} else if (dimensions instanceof DataFrameRow) {
|
|
1321
|
+
this._dimensions = DimensionVector.fromArray(dimensions);
|
|
1322
|
+
} else if (Array.isArray(dimensions)) {
|
|
1323
|
+
this._dimensions = new DimensionVector(...dimensions);
|
|
1324
|
+
} else {
|
|
1325
|
+
console.warn("Invalid dimensions provided, using default DimensionVector(0, 0)");
|
|
1326
|
+
this._dimensions = new DimensionVector(0, 0);
|
|
1327
|
+
}
|
|
1328
|
+
let data = { ...o };
|
|
1329
|
+
this._original = { ...data };
|
|
1330
|
+
this.ID = data.ID;
|
|
1331
|
+
this.x = data.x || this._dimensions[0];
|
|
1332
|
+
this.y = data.y || this._dimensions[1];
|
|
1333
|
+
this._size = data.size || 1;
|
|
1334
|
+
this._color = data.color || new Color("#000000");
|
|
1335
|
+
this.active = data.active || false;
|
|
1336
|
+
this.shape = data.shape || null;
|
|
1337
|
+
this.meta = data.meta || {};
|
|
1338
|
+
this._highlight = this._highlight_default;
|
|
1339
|
+
if (data.highlight) {
|
|
1340
|
+
this._highlight = data.highlight;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
get dimensions() {
|
|
1344
|
+
return this._dimensions;
|
|
1345
|
+
}
|
|
1346
|
+
get color() {
|
|
1347
|
+
return (
|
|
1348
|
+
// This was expecting hightight to be an object with a color property
|
|
1349
|
+
// but now it is just a Color object, so we can simplify this.
|
|
1350
|
+
this.highlighting && this._highlight ? this._highlight : this._color
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
set color(c) {
|
|
1354
|
+
this._color = c;
|
|
1355
|
+
}
|
|
1356
|
+
get highlight() {
|
|
1357
|
+
return this._highlight || this._highlight_default;
|
|
1358
|
+
}
|
|
1359
|
+
set highlight(h) {
|
|
1360
|
+
this._highlight = h;
|
|
1361
|
+
}
|
|
1362
|
+
get size() {
|
|
1363
|
+
return this._size;
|
|
1364
|
+
}
|
|
1365
|
+
set size(s) {
|
|
1366
|
+
this._size = s;
|
|
1367
|
+
}
|
|
1368
|
+
get original() {
|
|
1369
|
+
return this._original;
|
|
1370
|
+
}
|
|
1371
|
+
get x() {
|
|
1372
|
+
return this._x;
|
|
1373
|
+
}
|
|
1374
|
+
get y() {
|
|
1375
|
+
return this._y;
|
|
1376
|
+
}
|
|
1377
|
+
set x(x_) {
|
|
1378
|
+
this._x = x_;
|
|
1379
|
+
}
|
|
1380
|
+
set y(y_) {
|
|
1381
|
+
this._y = y_;
|
|
1382
|
+
}
|
|
1383
|
+
dimension(wh) {
|
|
1384
|
+
return this._dimensions.get(wh);
|
|
1385
|
+
}
|
|
1386
|
+
get transform() {
|
|
1387
|
+
return this._transform;
|
|
1388
|
+
}
|
|
1389
|
+
set transform(t) {
|
|
1390
|
+
this._transform = t;
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
__name(_PlottedObject, "PlottedObject");
|
|
1394
|
+
let PlottedObject = _PlottedObject;
|
|
1395
|
+
const _Line = class _Line extends PlottedObject {
|
|
1396
|
+
// constructor(x: LineOptions) {
|
|
1397
|
+
// super([], x);
|
|
1398
|
+
// // const data = Object.assign({}, x);
|
|
1399
|
+
// const { x1, y1, x2, y2, width, color, shape } = x;
|
|
1400
|
+
// // console.log("Line constructor data:", data);
|
|
1401
|
+
// // console.warn(" this was using `meta` but it is deprecated now - should be using `original` instead");
|
|
1402
|
+
// // this.meta = Object.keys(data)
|
|
1403
|
+
// // .filter(k => ['ID', 'x1', 'y1', 'x2', 'y2', 'size', 'color'].indexOf(k) < 0)
|
|
1404
|
+
// // .reduce((n: any, k: string) => { n[k] = data[k]; return n; }, {});
|
|
1405
|
+
// this.x1 = parseFloat(x1 as string);
|
|
1406
|
+
// this.y1 = parseFloat(y1 as string);
|
|
1407
|
+
// this.x2 = parseFloat(x2 as string);
|
|
1408
|
+
// this.y2 = parseFloat(y2 as string);
|
|
1409
|
+
// this.width = width;
|
|
1410
|
+
// this._color = color || new Color('#000000');
|
|
1411
|
+
// this.shape = shape || 'line';
|
|
1412
|
+
// }
|
|
1413
|
+
constructor(data, from, to) {
|
|
1414
|
+
super([], {});
|
|
1415
|
+
this.from = from;
|
|
1416
|
+
this.to = to;
|
|
1417
|
+
}
|
|
1418
|
+
get line() {
|
|
1419
|
+
return [[this.from.x, this.from.y], [this.to.x, this.to.y]];
|
|
1420
|
+
}
|
|
1421
|
+
get midpoint() {
|
|
1422
|
+
return [
|
|
1423
|
+
(this.from.x + this.to.x) / 2,
|
|
1424
|
+
(this.from.y + this.to.y) / 2
|
|
1425
|
+
];
|
|
1426
|
+
}
|
|
1427
|
+
get length() {
|
|
1428
|
+
return Math.sqrt(
|
|
1429
|
+
Math.pow(this.to.x - this.from.x, 2) + Math.pow(this.to.y - this.from.y, 2)
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
get color() {
|
|
1433
|
+
return this._color;
|
|
1434
|
+
}
|
|
1435
|
+
set color(c) {
|
|
1436
|
+
this._color = c;
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
__name(_Line, "Line");
|
|
1440
|
+
let Line = _Line;
|
|
1441
|
+
const _Point = class _Point extends PlottedObject {
|
|
1442
|
+
// constructor(dimensions: DimensionType, o: PointOptions) { //, graph?: any) {
|
|
1443
|
+
// super(dimensions, o);
|
|
1444
|
+
// this.active = o.active || false;
|
|
1445
|
+
// this.visible = o.visible || false;
|
|
1446
|
+
// this.size = o.size || 1;
|
|
1447
|
+
// this.color = o.color || new Color('#000000');
|
|
1448
|
+
// this.shape = o.shape ? o.shape : "circle";
|
|
1449
|
+
// this.scale_size = 2;
|
|
1450
|
+
// this._x = dimensions[0];
|
|
1451
|
+
// this._y = dimensions[1];
|
|
1452
|
+
// this.label = new Label(this.ID, this);
|
|
1453
|
+
// }
|
|
1454
|
+
constructor(data) {
|
|
1455
|
+
var _a, _b;
|
|
1456
|
+
let dimensions, meta, ID;
|
|
1457
|
+
if (data) {
|
|
1458
|
+
if (data instanceof DataFrameRow) {
|
|
1459
|
+
dimensions = (_a = data.classes) == null ? void 0 : _a.map((c, i) => c === "ena.co.occurrence" || c === "ena.dimension" ? data[i] : -1).filter((a) => a !== -1);
|
|
1460
|
+
meta = (_b = data.classes) == null ? void 0 : _b.map((c, i) => c === "ena.metadata" ? [data.names[i], data[i]] : null).filter((a) => a !== null).reduce((n, c) => {
|
|
1461
|
+
n[c[0]] = c[1];
|
|
1462
|
+
return n;
|
|
1463
|
+
}, {});
|
|
1464
|
+
ID = data.rowName || `point-${Math.random().toString(36).substring(2, 9)}`;
|
|
1465
|
+
} else if (typeof data === "object") {
|
|
1466
|
+
dimensions = data.dimensions || [];
|
|
1467
|
+
meta = data.meta || {};
|
|
1468
|
+
ID = data.ID || `point-${Math.random().toString(36).substring(2, 9)}`;
|
|
1469
|
+
} else {
|
|
1470
|
+
console.warn("Invalid data provided for Point, using default values");
|
|
1471
|
+
dimensions = [];
|
|
1472
|
+
meta = {};
|
|
1473
|
+
ID = `point-${Math.random().toString(36).substring(2, 9)}`;
|
|
1474
|
+
}
|
|
1475
|
+
} else {
|
|
1476
|
+
dimensions = [];
|
|
1477
|
+
meta = {};
|
|
1478
|
+
ID = `point-${Math.random().toString(36).substring(2, 9)}`;
|
|
1479
|
+
}
|
|
1480
|
+
super(dimensions ?? [], { meta, ID });
|
|
1481
|
+
this.edges = /* @__PURE__ */ new Map();
|
|
1482
|
+
this.shape = _Point.SHAPES.CIRCLE;
|
|
1483
|
+
}
|
|
1484
|
+
set label(l) {
|
|
1485
|
+
this._label = l;
|
|
1486
|
+
}
|
|
1487
|
+
get label() {
|
|
1488
|
+
return this._label;
|
|
1489
|
+
}
|
|
1490
|
+
get x() {
|
|
1491
|
+
return this._x;
|
|
1492
|
+
}
|
|
1493
|
+
get y() {
|
|
1494
|
+
return this._y;
|
|
1495
|
+
}
|
|
1496
|
+
set x(x_) {
|
|
1497
|
+
this._x = parseFloat(x_);
|
|
1498
|
+
}
|
|
1499
|
+
set y(y_) {
|
|
1500
|
+
this._y = parseFloat(y_);
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
__name(_Point, "Point");
|
|
1504
|
+
_Point.SHAPES = { CIRCLE: "circle", SQUARE: "rect" };
|
|
1505
|
+
let Point = _Point;
|
|
1506
|
+
const _Label = class _Label extends PlottedObject {
|
|
1507
|
+
constructor(txt, object) {
|
|
1508
|
+
super(object.dimensions, object);
|
|
1509
|
+
this._visible = false;
|
|
1510
|
+
this.offset = 0;
|
|
1511
|
+
this.text = txt;
|
|
1512
|
+
this.ID = object.ID;
|
|
1513
|
+
this.object = object;
|
|
1514
|
+
this.x = this.dimensions[0];
|
|
1515
|
+
this.y = this.dimensions[1];
|
|
1516
|
+
this.offset = object.size || 0;
|
|
1517
|
+
this.size = this.offset / 4;
|
|
1518
|
+
this.visible = false;
|
|
1519
|
+
this._transform.set("e", 1);
|
|
1520
|
+
this._transform.set("f", 1);
|
|
1521
|
+
}
|
|
1522
|
+
get x() {
|
|
1523
|
+
return parseFloat(this._x);
|
|
1524
|
+
}
|
|
1525
|
+
get y() {
|
|
1526
|
+
return parseFloat(this._y);
|
|
1527
|
+
}
|
|
1528
|
+
set x(x_) {
|
|
1529
|
+
this._x = x_;
|
|
1530
|
+
}
|
|
1531
|
+
set y(y_) {
|
|
1532
|
+
this._y = y_;
|
|
1533
|
+
}
|
|
1534
|
+
get transform() {
|
|
1535
|
+
console.warn("transform is disabled - need updated reference for the graph options");
|
|
1536
|
+
return this._transform;
|
|
1537
|
+
}
|
|
1538
|
+
set transform(t) {
|
|
1539
|
+
this._transform = t;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
__name(_Label, "Label");
|
|
1543
|
+
_Label.DISPLAY_TYPES = { OFF: 0, AUTO: 1, CLICK: 2, ON: 3 };
|
|
1544
|
+
_Label.HIGHLIGHT = { OFF: 0, ON: 2 };
|
|
1545
|
+
_Label.POSITION = { OBJECT: 1, STATIC: 2, CONTAINER: 3, CUSTOM: 4 };
|
|
1546
|
+
let Label = _Label;
|
|
1547
|
+
const _Extremes = class _Extremes {
|
|
1548
|
+
// _point_max?: Point;
|
|
1549
|
+
// _node_max?: Point;
|
|
1550
|
+
constructor(x, y) {
|
|
1551
|
+
this.x_max = x;
|
|
1552
|
+
this.y_max = y;
|
|
1553
|
+
}
|
|
1554
|
+
as_viewBox(p = 1.2) {
|
|
1555
|
+
let x = this.x_max * p;
|
|
1556
|
+
let y = this.y_max * p;
|
|
1557
|
+
return `${x * -1} ${y * -1} ${x * 2} ${y * 2}`;
|
|
1558
|
+
}
|
|
1559
|
+
// get point_max(): Point {
|
|
1560
|
+
// return this._point_max || { x: 1, y: 1 };
|
|
1561
|
+
// }
|
|
1562
|
+
// get node_max(): Point {
|
|
1563
|
+
// return this._node_max || { x: 1, y: 1 };
|
|
1564
|
+
// }
|
|
1565
|
+
get height() {
|
|
1566
|
+
return this.y_max * 2;
|
|
1567
|
+
}
|
|
1568
|
+
get width() {
|
|
1569
|
+
return this.x_max * 2;
|
|
1570
|
+
}
|
|
1571
|
+
get top() {
|
|
1572
|
+
return this.y_max * -1;
|
|
1573
|
+
}
|
|
1574
|
+
get bottom() {
|
|
1575
|
+
return this.y_max;
|
|
1576
|
+
}
|
|
1577
|
+
get left() {
|
|
1578
|
+
return this.x_max * -1;
|
|
1579
|
+
}
|
|
1580
|
+
get right() {
|
|
1581
|
+
return this.x_max;
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
__name(_Extremes, "Extremes");
|
|
1585
|
+
let Extremes = _Extremes;
|
|
1586
|
+
const _AxisPoint = class _AxisPoint extends PlottedObject {
|
|
1587
|
+
constructor(dimensions, o) {
|
|
1588
|
+
super(dimensions, o);
|
|
1589
|
+
this.visible = true;
|
|
1590
|
+
this.size = o.size;
|
|
1591
|
+
}
|
|
1592
|
+
set size(s) {
|
|
1593
|
+
this._size = s;
|
|
1594
|
+
}
|
|
1595
|
+
get size() {
|
|
1596
|
+
return this._size;
|
|
1597
|
+
}
|
|
1598
|
+
get x() {
|
|
1599
|
+
return this._x;
|
|
1600
|
+
}
|
|
1601
|
+
get y() {
|
|
1602
|
+
return this._y;
|
|
1603
|
+
}
|
|
1604
|
+
set x(x_) {
|
|
1605
|
+
this._x = x_;
|
|
1606
|
+
}
|
|
1607
|
+
set y(y_) {
|
|
1608
|
+
this._y = y_;
|
|
1609
|
+
}
|
|
1610
|
+
get transform() {
|
|
1611
|
+
return this._transform;
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
__name(_AxisPoint, "AxisPoint");
|
|
1615
|
+
let AxisPoint = _AxisPoint;
|
|
1616
|
+
let DefaultGraphOptions = {
|
|
1617
|
+
GRAPH: {
|
|
1618
|
+
ratio: 1,
|
|
1619
|
+
x_max: 1,
|
|
1620
|
+
y_max: 1,
|
|
1621
|
+
selector: "body",
|
|
1622
|
+
width: 1,
|
|
1623
|
+
height: 1,
|
|
1624
|
+
axes: true,
|
|
1625
|
+
SIZE_EFFECT: 1,
|
|
1626
|
+
dimensions: [0, 1]
|
|
1627
|
+
},
|
|
1628
|
+
NODE: {
|
|
1629
|
+
size: 1,
|
|
1630
|
+
highlight: {
|
|
1631
|
+
color: "yellow",
|
|
1632
|
+
labels: Label.DISPLAY_TYPES.ON
|
|
1633
|
+
},
|
|
1634
|
+
labels: Label.DISPLAY_TYPES.ON,
|
|
1635
|
+
labelposition: Label.POSITION.OBJECT
|
|
1636
|
+
},
|
|
1637
|
+
POINT: {
|
|
1638
|
+
active: 2,
|
|
1639
|
+
size: 0.05,
|
|
1640
|
+
color: "#000",
|
|
1641
|
+
shape: Point.SHAPES.CIRCLE,
|
|
1642
|
+
labels: Label.DISPLAY_TYPES.AUTO,
|
|
1643
|
+
labelposition: Label.POSITION.OBJECT
|
|
1644
|
+
},
|
|
1645
|
+
MEAN: {
|
|
1646
|
+
shape: Point.SHAPES.SQUARE,
|
|
1647
|
+
labels: Label.DISPLAY_TYPES.AUTO
|
|
1648
|
+
},
|
|
1649
|
+
LABEL: {
|
|
1650
|
+
highlight: Label.HIGHLIGHT.OFF,
|
|
1651
|
+
position: ["top", "left"],
|
|
1652
|
+
container: "#label-container"
|
|
1653
|
+
},
|
|
1654
|
+
EDGE: {
|
|
1655
|
+
multiplier: 1
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
const _Graph = class _Graph {
|
|
1659
|
+
constructor(options = { ...DefaultGraphOptions }) {
|
|
1660
|
+
this.__state__ = new EventBus();
|
|
1661
|
+
this.name = "unnamed-graph";
|
|
1662
|
+
this.origin = void 0;
|
|
1663
|
+
this.points = /* @__PURE__ */ new Map();
|
|
1664
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
1665
|
+
this.edges = /* @__PURE__ */ new Map();
|
|
1666
|
+
this.labels = /* @__PURE__ */ new Map();
|
|
1667
|
+
this.transform = new Transform();
|
|
1668
|
+
this.scale_x = 1;
|
|
1669
|
+
this.scale_y = 1;
|
|
1670
|
+
this.SIZE_EFFECT = 1;
|
|
1671
|
+
this._mirror_y = true;
|
|
1672
|
+
this._mirror_x = false;
|
|
1673
|
+
this._layers = new LayerArray(
|
|
1674
|
+
{ name: "graph", tabindex: 0, layers: [
|
|
1675
|
+
{
|
|
1676
|
+
name: "axes",
|
|
1677
|
+
layers: [
|
|
1678
|
+
{
|
|
1679
|
+
name: "axis_lines",
|
|
1680
|
+
tabindex: 0,
|
|
1681
|
+
layers: [],
|
|
1682
|
+
objects: []
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "axis_labels",
|
|
1686
|
+
tabindex: 0,
|
|
1687
|
+
layers: [],
|
|
1688
|
+
objects: []
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
name: "axis_points",
|
|
1692
|
+
tabindex: 0,
|
|
1693
|
+
layers: [],
|
|
1694
|
+
objects: [
|
|
1695
|
+
this.origin
|
|
1696
|
+
]
|
|
1697
|
+
}
|
|
1698
|
+
]
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
name: "network",
|
|
1702
|
+
layers: [
|
|
1703
|
+
{ name: "edges", layers: [], tabindex: 0 },
|
|
1704
|
+
{ name: "nodes", layers: [], tabindex: 0 },
|
|
1705
|
+
{ name: "edgenodes", layers: [], tabindex: 0 }
|
|
1706
|
+
]
|
|
1707
|
+
},
|
|
1708
|
+
{ name: "points", layers: [], tabindex: 0 },
|
|
1709
|
+
{ name: "labels", layers: [], tabindex: 0 }
|
|
1710
|
+
] }
|
|
1711
|
+
);
|
|
1712
|
+
this.options = options;
|
|
1713
|
+
this.origin = new AxisPoint(
|
|
1714
|
+
new DimensionVector(0, 0),
|
|
1715
|
+
{
|
|
1716
|
+
ID: "origin",
|
|
1717
|
+
x: 0,
|
|
1718
|
+
y: 0,
|
|
1719
|
+
size: 0,
|
|
1720
|
+
meta: {},
|
|
1721
|
+
graph: this,
|
|
1722
|
+
color: new Color("#FFFFFF"),
|
|
1723
|
+
highlight: new Color("#FFFFFF"),
|
|
1724
|
+
active: false
|
|
1725
|
+
}
|
|
1726
|
+
);
|
|
1727
|
+
let layer_id = 1;
|
|
1728
|
+
function buildLayerTree(layerData, parent, graph) {
|
|
1729
|
+
const new_layer = new Layer(`${layer_id}`, layerData, graph);
|
|
1730
|
+
layer_id += 1;
|
|
1731
|
+
new_layer.parent = parent || void 0;
|
|
1732
|
+
if (layerData.layers && Array.isArray(layerData.layers)) {
|
|
1733
|
+
new_layer.layers = new LayerArray(...layerData.layers.map((child) => buildLayerTree(child, new_layer, graph)));
|
|
1734
|
+
}
|
|
1735
|
+
return new_layer;
|
|
1736
|
+
}
|
|
1737
|
+
__name(buildLayerTree, "buildLayerTree");
|
|
1738
|
+
this.layers = new LayerArray(...this._layers.map((l) => buildLayerTree(l, null, this)));
|
|
1739
|
+
let axisPointLayer = this.layers.search("layers", "axis_points");
|
|
1740
|
+
if (axisPointLayer) {
|
|
1741
|
+
axisPointLayer.objects.set(this.origin.ID, this.origin);
|
|
1742
|
+
}
|
|
1743
|
+
let axisLineLayer = this.layers.search("layers", "axis_lines");
|
|
1744
|
+
if (axisLineLayer) {
|
|
1745
|
+
let extremes = this.extremes || new Extremes(1, 1);
|
|
1746
|
+
let axis_x = new Line(
|
|
1747
|
+
{},
|
|
1748
|
+
new Point({ ID: "axis-x-start", dimensions: [-extremes.x_max, 0], classes: ["ena.dimension", "ena.dimension"] }),
|
|
1749
|
+
new Point({ ID: "axis-x-end", dimensions: [extremes.x_max, 0], classes: ["ena.dimension", "ena.dimension"] })
|
|
1750
|
+
);
|
|
1751
|
+
axis_x.ID = "axis-x";
|
|
1752
|
+
axisLineLayer.objects.set(axis_x.ID, axis_x);
|
|
1753
|
+
let axis_y = new Line(
|
|
1754
|
+
{},
|
|
1755
|
+
new Point({ ID: "axis-y-start", dimensions: [0, -extremes.y_max], classes: ["ena.dimension", "ena.dimension"] }),
|
|
1756
|
+
new Point({ ID: "axis-y-end", dimensions: [0, extremes.y_max], classes: ["ena.dimension", "ena.dimension"] })
|
|
1757
|
+
);
|
|
1758
|
+
axis_y.ID = "axis-y";
|
|
1759
|
+
axisLineLayer.objects.set(axis_y.ID, axis_y);
|
|
1760
|
+
console.log("Added axis lines to layer: ", axisLineLayer);
|
|
1761
|
+
}
|
|
1762
|
+
this.extremes = new Extremes(options.GRAPH.x_max, options.GRAPH.y_max);
|
|
1763
|
+
}
|
|
1764
|
+
set extremes(e) {
|
|
1765
|
+
this._extremes = e;
|
|
1766
|
+
}
|
|
1767
|
+
get extremes() {
|
|
1768
|
+
return this._extremes;
|
|
1769
|
+
}
|
|
1770
|
+
get mirror_x() {
|
|
1771
|
+
return this._mirror_x;
|
|
1772
|
+
}
|
|
1773
|
+
set mirror_x(m) {
|
|
1774
|
+
this._mirror_x = m;
|
|
1775
|
+
}
|
|
1776
|
+
get mirror_y() {
|
|
1777
|
+
return this._mirror_y;
|
|
1778
|
+
}
|
|
1779
|
+
set mirror_y(m) {
|
|
1780
|
+
this._mirror_y = m;
|
|
1781
|
+
}
|
|
1782
|
+
x_mirror() {
|
|
1783
|
+
return this.mirror_x ? -1 : 1;
|
|
1784
|
+
}
|
|
1785
|
+
y_mirror() {
|
|
1786
|
+
return this.mirror_y ? -1 : 1;
|
|
1787
|
+
}
|
|
1788
|
+
add_point(p) {
|
|
1789
|
+
const point = new Point(p);
|
|
1790
|
+
this.points.set(point.ID, point);
|
|
1791
|
+
}
|
|
1792
|
+
add_points(ps) {
|
|
1793
|
+
let g_ = this;
|
|
1794
|
+
ps.rows().forEach((p) => {
|
|
1795
|
+
g_.add_point(p);
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
add_nodes(ns) {
|
|
1799
|
+
let g_ = this;
|
|
1800
|
+
ns.forEach((new_node) => {
|
|
1801
|
+
new_node.label = new Label(new_node.ID, new_node);
|
|
1802
|
+
g_.labels.set(new_node.label.ID, new_node.label);
|
|
1803
|
+
g_.nodes.set(new_node.ID, new_node);
|
|
1804
|
+
const nodesLayer = g_.layers.search("layers", "nodes");
|
|
1805
|
+
if (nodesLayer) {
|
|
1806
|
+
nodesLayer.objects.push(new_node);
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
g_.update_extremes();
|
|
1810
|
+
}
|
|
1811
|
+
add_edge(e, from, to) {
|
|
1812
|
+
let g_ = this;
|
|
1813
|
+
g_.edges.set(e.ID, e);
|
|
1814
|
+
this.add_nodes([from || e.from, to || e.to]);
|
|
1815
|
+
return e;
|
|
1816
|
+
}
|
|
1817
|
+
add_edges(es) {
|
|
1818
|
+
let added = es.map((e) => this.add_edge(e));
|
|
1819
|
+
const edgesLayer = this.layers.search("layers", "edges");
|
|
1820
|
+
if (edgesLayer) {
|
|
1821
|
+
edgesLayer.objects.push(...added);
|
|
1822
|
+
}
|
|
1823
|
+
return added;
|
|
1824
|
+
}
|
|
1825
|
+
update_extremes() {
|
|
1826
|
+
const search_layers = [
|
|
1827
|
+
this.layers.search("layers", "points"),
|
|
1828
|
+
this.layers.search("layers", "nodes")
|
|
1829
|
+
].filter((l) => l !== void 0);
|
|
1830
|
+
if (search_layers.length === 0) {
|
|
1831
|
+
this.extremes = new Extremes(1, 1);
|
|
1832
|
+
} else {
|
|
1833
|
+
const found_max = Math.max(...search_layers.map((l) => Math.max(...[...l.objects.values()].map((o) => Math.max(Math.abs(o.x), Math.abs(o.y))))).filter((a) => Math.abs(a) !== Infinity)) * 1.5;
|
|
1834
|
+
console.log("Found max for extremes:", found_max);
|
|
1835
|
+
this.extremes = new Extremes(Math.ceil(found_max), Math.ceil(found_max));
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
base_point_size(scale = 8) {
|
|
1839
|
+
let minDim = Math.min(this.extremes.x_max, this.extremes.y_max);
|
|
1840
|
+
let n = this.points.size + this.nodes.size;
|
|
1841
|
+
return Math.min(1, minDim / (Math.sqrt(n) * scale));
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
__name(_Graph, "Graph");
|
|
1845
|
+
let Graph = _Graph;
|
|
1846
|
+
const _DrawableElement = class _DrawableElement extends HTMLElement {
|
|
1847
|
+
constructor() {
|
|
1848
|
+
super();
|
|
1849
|
+
this.element = null;
|
|
1850
|
+
this.object = null;
|
|
1851
|
+
const element = this, shadow = element.attachShadow({ mode: "open" }), style = document.createElement("style"), wrapper = document.createElement("div");
|
|
1852
|
+
style.textContent = this.constructor.style;
|
|
1853
|
+
shadow.appendChild(style);
|
|
1854
|
+
wrapper.setAttribute("class", "wrapper");
|
|
1855
|
+
shadow.appendChild(wrapper);
|
|
1856
|
+
}
|
|
1857
|
+
get wrapper() {
|
|
1858
|
+
const element = this, shadow = element.shadowRoot, wrapper = shadow == null ? void 0 : shadow.querySelector(".wrapper");
|
|
1859
|
+
return wrapper;
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
__name(_DrawableElement, "DrawableElement");
|
|
1863
|
+
_DrawableElement.style = `
|
|
1864
|
+
:host {
|
|
1865
|
+
max-width: 100%;
|
|
1866
|
+
display: block;
|
|
1867
|
+
}
|
|
1868
|
+
.wrapper {
|
|
1869
|
+
height: 100%;
|
|
1870
|
+
display: flex;
|
|
1871
|
+
justify-content: center;
|
|
1872
|
+
}
|
|
1873
|
+
`;
|
|
1874
|
+
let DrawableElement = _DrawableElement;
|
|
1875
|
+
const zoom_level = 1;
|
|
1876
|
+
const _Edge = class _Edge extends Line {
|
|
1877
|
+
constructor(data, from, to, weight = 0) {
|
|
1878
|
+
super(data, from, to);
|
|
1879
|
+
this._totalweight = 0;
|
|
1880
|
+
this._weight = weight;
|
|
1881
|
+
}
|
|
1882
|
+
get base_color() {
|
|
1883
|
+
return this._color;
|
|
1884
|
+
}
|
|
1885
|
+
get color() {
|
|
1886
|
+
const base = this._color || new Color("#000000");
|
|
1887
|
+
return new WeightedColor(base, Math.abs(this.weight));
|
|
1888
|
+
}
|
|
1889
|
+
set color(c) {
|
|
1890
|
+
this._color = c;
|
|
1891
|
+
}
|
|
1892
|
+
base_weight() {
|
|
1893
|
+
return this._weight;
|
|
1894
|
+
}
|
|
1895
|
+
get weight() {
|
|
1896
|
+
let w = Array.isArray(this._weight) ? this._weight[0] - this._weight[1] : this._weight;
|
|
1897
|
+
return w;
|
|
1898
|
+
}
|
|
1899
|
+
set weight(w) {
|
|
1900
|
+
this._weight = w;
|
|
1901
|
+
}
|
|
1902
|
+
get totalweight() {
|
|
1903
|
+
return this.weight;
|
|
1904
|
+
}
|
|
1905
|
+
set totalweight(w) {
|
|
1906
|
+
this._totalweight = w;
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
__name(_Edge, "Edge");
|
|
1910
|
+
let Edge = _Edge;
|
|
1911
|
+
const _EdgeArrow = class _EdgeArrow extends PlottedObject {
|
|
1912
|
+
constructor(edge) {
|
|
1913
|
+
super(edge.dimensions || [], edge);
|
|
1914
|
+
__privateAdd(this, _edge);
|
|
1915
|
+
__privateSet(this, _edge, edge);
|
|
1916
|
+
}
|
|
1917
|
+
get edge() {
|
|
1918
|
+
return __privateGet(this, _edge);
|
|
1919
|
+
}
|
|
1920
|
+
get path() {
|
|
1921
|
+
let _this = __privateGet(this, _edge);
|
|
1922
|
+
let _mid = _this.from_point, head = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.7), nock = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.85), mid = Tools.Geometry.Line.midpoint(head, nock), rot_mid = Tools.Geometry.Line.rotate(_this.line, mid), rot_nock = Tools.Geometry.Line.rotate(_this.line, nock), mid_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_mid), mid_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_mid), nock_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_nock), nock_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_nock);
|
|
1923
|
+
let p = `M${head.join(" ")} ${mid_int0.join(" ")} ${nock_int0.join(" ")} ${mid.join(" ")} ${nock_int1.join(" ")} ${mid_int1.join(" ")}z`;
|
|
1924
|
+
return p;
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
_edge = new WeakMap();
|
|
1928
|
+
__name(_EdgeArrow, "EdgeArrow");
|
|
1929
|
+
let EdgeArrow = _EdgeArrow;
|
|
1930
|
+
const _TaperedEdge = class _TaperedEdge extends Edge {
|
|
1931
|
+
constructor(data, from, to) {
|
|
1932
|
+
super(data, from, to);
|
|
1933
|
+
__privateAdd(this, _with_arrow);
|
|
1934
|
+
__privateAdd(this, _arrow);
|
|
1935
|
+
this._path = "";
|
|
1936
|
+
__privateSet(this, _with_arrow, false);
|
|
1937
|
+
this._directions = [
|
|
1938
|
+
"from",
|
|
1939
|
+
"to"
|
|
1940
|
+
/* TO */
|
|
1941
|
+
];
|
|
1942
|
+
this.towards = "to";
|
|
1943
|
+
if (__privateGet(this, _with_arrow) === true) {
|
|
1944
|
+
__privateSet(this, _arrow, new EdgeArrow(this));
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
get path() {
|
|
1948
|
+
return `M${this.from.x} ${this.from.y} L${this.base_pts[0].join(" ")} L${this.base_pts[1].join(" ")}z`;
|
|
1949
|
+
}
|
|
1950
|
+
static calculate_base_points(edg) {
|
|
1951
|
+
const top = edg.towards;
|
|
1952
|
+
const base = edg._directions.find((d) => d !== edg.towards);
|
|
1953
|
+
const bx = edg[base].x, by = edg[base].y;
|
|
1954
|
+
const tx = edg[top].x, ty = edg[top].y;
|
|
1955
|
+
const dx = tx - bx, dy = ty - by;
|
|
1956
|
+
const L = Math.hypot(dx, dy);
|
|
1957
|
+
const wt = (edg.weight ?? 0) * zoom_level;
|
|
1958
|
+
if (L === 0 || !isFinite(wt) || wt === 0) {
|
|
1959
|
+
return [[tx, ty], [tx, ty]];
|
|
1960
|
+
}
|
|
1961
|
+
const px = -dy / L, py = dx / L;
|
|
1962
|
+
const halfW = wt / 2;
|
|
1963
|
+
return [
|
|
1964
|
+
[tx + px * halfW, ty + py * halfW],
|
|
1965
|
+
[tx - px * halfW, ty - py * halfW]
|
|
1966
|
+
];
|
|
1967
|
+
}
|
|
1968
|
+
get base_pts() {
|
|
1969
|
+
return _TaperedEdge.calculate_base_points(this);
|
|
1970
|
+
}
|
|
1971
|
+
get arrow() {
|
|
1972
|
+
return __privateGet(this, _arrow);
|
|
1973
|
+
}
|
|
1974
|
+
get with_arrow() {
|
|
1975
|
+
return __privateGet(this, _with_arrow);
|
|
1976
|
+
}
|
|
1977
|
+
get from_point() {
|
|
1978
|
+
return [this.from.x, this.from.y];
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
_with_arrow = new WeakMap();
|
|
1982
|
+
_arrow = new WeakMap();
|
|
1983
|
+
__name(_TaperedEdge, "TaperedEdge");
|
|
1984
|
+
let TaperedEdge = _TaperedEdge;
|
|
1985
|
+
const _TaperedWeightedMidpointEdge = class _TaperedWeightedMidpointEdge extends TaperedEdge {
|
|
1986
|
+
constructor(data, from, to) {
|
|
1987
|
+
super(data, from, to);
|
|
1988
|
+
this._dataWeight = 0;
|
|
1989
|
+
this._totalweight = data.totalweight ?? 0;
|
|
1990
|
+
this._dataWeight = data.weight ?? this._weight;
|
|
1991
|
+
}
|
|
1992
|
+
get weighted_midpoint() {
|
|
1993
|
+
return Tools.Geometry.Line.interpolate(this.line)(1 - this._dataWeight / this._totalweight);
|
|
1994
|
+
}
|
|
1995
|
+
get base_pts() {
|
|
1996
|
+
return TaperedEdge.calculate_base_points(this);
|
|
1997
|
+
}
|
|
1998
|
+
static build_path(edg) {
|
|
1999
|
+
if (edg._totalweight > 0) {
|
|
2000
|
+
let p = `M${edg.weighted_midpoint[0]} ${edg.weighted_midpoint[1]} L${edg.base_pts[0].join(" ")} L${edg.base_pts[1].join(" ")}z`;
|
|
2001
|
+
return p;
|
|
2002
|
+
} else {
|
|
2003
|
+
console.error("TaperedWeightedMidpointEdge: No totalweight, returning empty path");
|
|
2004
|
+
return "";
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
get path() {
|
|
2008
|
+
return _TaperedWeightedMidpointEdge.build_path(this);
|
|
2009
|
+
}
|
|
2010
|
+
get from_point() {
|
|
2011
|
+
return this.weighted_midpoint;
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
__name(_TaperedWeightedMidpointEdge, "TaperedWeightedMidpointEdge");
|
|
2015
|
+
let TaperedWeightedMidpointEdge = _TaperedWeightedMidpointEdge;
|
|
2016
|
+
const _NodeEdge = class _NodeEdge extends Edge {
|
|
2017
|
+
constructor(data, from, to) {
|
|
2018
|
+
super(data, from, to);
|
|
2019
|
+
}
|
|
2020
|
+
static build_path(weight, to) {
|
|
2021
|
+
let R = weight / 2, CX = to.x - R, R2 = R * 2, p = `M ${CX}, ${to.y} a ${R},${R} 0 1,0 ${R2},0 a ${R},${R} 0 1,0 -${R2},0`;
|
|
2022
|
+
return p;
|
|
2023
|
+
}
|
|
2024
|
+
get path() {
|
|
2025
|
+
let wt = this.weight * zoom_level;
|
|
2026
|
+
return _NodeEdge.build_path(wt, this.to);
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
__name(_NodeEdge, "NodeEdge");
|
|
2030
|
+
let NodeEdge = _NodeEdge;
|
|
2031
|
+
const _Drawable = class _Drawable {
|
|
2032
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2033
|
+
this.__data__ = __data__;
|
|
2034
|
+
this.parent = parent;
|
|
2035
|
+
this.graph = graph;
|
|
2036
|
+
this.__children__ = __children__;
|
|
2037
|
+
this.svg_element = this.create();
|
|
2038
|
+
}
|
|
2039
|
+
get_size() {
|
|
2040
|
+
return this.__data__.size;
|
|
2041
|
+
}
|
|
2042
|
+
draw() {
|
|
2043
|
+
var _a;
|
|
2044
|
+
(_a = this.parent) == null ? void 0 : _a.appendChild(this.svg_element);
|
|
2045
|
+
}
|
|
2046
|
+
update(elem, animate = true) {
|
|
2047
|
+
}
|
|
2048
|
+
animateSVGAttribute(elem, attr, to) {
|
|
2049
|
+
const from = parseFloat(elem.getAttribute(attr) || "0");
|
|
2050
|
+
if (from === to) return;
|
|
2051
|
+
const duration = 5e3;
|
|
2052
|
+
const start = performance.now();
|
|
2053
|
+
function animate(now) {
|
|
2054
|
+
const t = Math.min(1, (now - start) / duration);
|
|
2055
|
+
const value = from + (to - from) * t;
|
|
2056
|
+
elem.setAttribute(attr, value.toString());
|
|
2057
|
+
if (t < 1) requestAnimationFrame(animate);
|
|
2058
|
+
else elem.setAttribute(attr, to.toString());
|
|
2059
|
+
}
|
|
2060
|
+
__name(animate, "animate");
|
|
2061
|
+
requestAnimationFrame(animate);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
__name(_Drawable, "Drawable");
|
|
2065
|
+
let Drawable = _Drawable;
|
|
2066
|
+
const _QELine = class _QELine extends Drawable {
|
|
2067
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2068
|
+
super(__data__, parent, graph, __children__);
|
|
2069
|
+
this.__data__ = __data__;
|
|
2070
|
+
this.parent = parent;
|
|
2071
|
+
this.graph = graph;
|
|
2072
|
+
this.__children__ = __children__;
|
|
2073
|
+
this.__name__ = "unnamed-line";
|
|
2074
|
+
this._arrow_el = null;
|
|
2075
|
+
this.svg_element = this.create();
|
|
2076
|
+
}
|
|
2077
|
+
create() {
|
|
2078
|
+
if (this.__data__ instanceof NodeEdge) {
|
|
2079
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2080
|
+
path.setAttribute("fill", this.__data__.color.toString());
|
|
2081
|
+
path.setAttribute("stroke", "none");
|
|
2082
|
+
this.svg_element = path;
|
|
2083
|
+
return path;
|
|
2084
|
+
}
|
|
2085
|
+
if (this.__data__ instanceof TaperedEdge) {
|
|
2086
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2087
|
+
path.setAttribute("fill", this.__data__.color.toString());
|
|
2088
|
+
path.setAttribute("stroke", "none");
|
|
2089
|
+
this.svg_element = path;
|
|
2090
|
+
return path;
|
|
2091
|
+
}
|
|
2092
|
+
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
2093
|
+
line.setAttribute("x1", this.__data__.from.x.toString());
|
|
2094
|
+
line.setAttribute("y1", this.__data__.from.y.toString());
|
|
2095
|
+
line.setAttribute("x2", this.__data__.to.x.toString());
|
|
2096
|
+
line.setAttribute("y2", this.__data__.to.y.toString());
|
|
2097
|
+
line.setAttribute("stroke-width", this.get_size().toString());
|
|
2098
|
+
line.setAttribute("stroke", this.__data__.color.toString());
|
|
2099
|
+
this.svg_element = line;
|
|
2100
|
+
return line;
|
|
2101
|
+
}
|
|
2102
|
+
update() {
|
|
2103
|
+
var _a;
|
|
2104
|
+
if (!this.svg_element) {
|
|
2105
|
+
this.create();
|
|
2106
|
+
}
|
|
2107
|
+
if (!this.svg_element.parentElement) {
|
|
2108
|
+
this.parent.appendChild(this.svg_element);
|
|
2109
|
+
}
|
|
2110
|
+
const extent = ((_a = this.graph.__data__.extremes) == null ? void 0 : _a.x_max) ?? 1;
|
|
2111
|
+
const meta = this.__data__.meta ?? {};
|
|
2112
|
+
if (meta.isMeanEdge) {
|
|
2113
|
+
const weight = typeof meta.weight === "number" ? meta.weight : 0;
|
|
2114
|
+
const maxW = typeof meta.maxWeight === "number" ? meta.maxWeight : 1;
|
|
2115
|
+
const opacity = typeof meta.opacity === "number" ? meta.opacity : Math.min(1, weight / (maxW || 1));
|
|
2116
|
+
const sw = (extent > 0 ? extent : 1) * 0.011 * (weight / (maxW || 1));
|
|
2117
|
+
const baseColor = this.__data__._color || new Color("#000000");
|
|
2118
|
+
const edgeColor = new WeightedColor(baseColor, opacity).toString();
|
|
2119
|
+
if (this.__data__ instanceof NodeEdge) {
|
|
2120
|
+
const nodeSize = typeof meta.nodeSize === "number" ? meta.nodeSize : 2;
|
|
2121
|
+
const nodeR = extent * 6e-3 * nodeSize;
|
|
2122
|
+
this.__data__.weight = 2 * nodeR * 0.55 * opacity;
|
|
2123
|
+
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2124
|
+
this.svg_element.setAttribute("fill", edgeColor);
|
|
2125
|
+
this.svg_element.setAttribute("stroke", "none");
|
|
2126
|
+
} else if (this.__data__ instanceof TaperedEdge) {
|
|
2127
|
+
this.__data__.weight = sw * 4;
|
|
2128
|
+
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2129
|
+
this.svg_element.setAttribute("fill", edgeColor);
|
|
2130
|
+
this.svg_element.setAttribute("stroke", "none");
|
|
2131
|
+
if (meta.isDirected) {
|
|
2132
|
+
try {
|
|
2133
|
+
const arrow = new EdgeArrow(this.__data__);
|
|
2134
|
+
const arrowPath = arrow.path;
|
|
2135
|
+
if (arrowPath && !arrowPath.includes("NaN") && !arrowPath.includes("Infinity")) {
|
|
2136
|
+
if (!this._arrow_el) {
|
|
2137
|
+
this._arrow_el = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2138
|
+
this._arrow_el.setAttribute("stroke", "none");
|
|
2139
|
+
}
|
|
2140
|
+
if (!this._arrow_el.parentElement) {
|
|
2141
|
+
this.parent.appendChild(this._arrow_el);
|
|
2142
|
+
}
|
|
2143
|
+
this._arrow_el.setAttribute("d", arrowPath);
|
|
2144
|
+
const arrowColor = baseColor.toString();
|
|
2145
|
+
this._arrow_el.setAttribute("fill", arrowColor);
|
|
2146
|
+
}
|
|
2147
|
+
} catch (_) {
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
} else {
|
|
2151
|
+
this.svg_element.setAttribute("x1", this.__data__.from.x.toString());
|
|
2152
|
+
this.svg_element.setAttribute("y1", this.__data__.from.y.toString());
|
|
2153
|
+
this.svg_element.setAttribute("x2", this.__data__.to.x.toString());
|
|
2154
|
+
this.svg_element.setAttribute("y2", this.__data__.to.y.toString());
|
|
2155
|
+
this.svg_element.setAttribute("stroke-width", sw.toString());
|
|
2156
|
+
this.svg_element.setAttribute("stroke", edgeColor);
|
|
2157
|
+
this.svg_element.removeAttribute("marker-end");
|
|
2158
|
+
}
|
|
2159
|
+
} else {
|
|
2160
|
+
this.svg_element.setAttribute("x1", this.__data__.from.x.toString());
|
|
2161
|
+
this.svg_element.setAttribute("y1", this.__data__.from.y.toString());
|
|
2162
|
+
this.svg_element.setAttribute("x2", this.__data__.to.x.toString());
|
|
2163
|
+
this.svg_element.setAttribute("y2", this.__data__.to.y.toString());
|
|
2164
|
+
const sw = (extent > 0 ? extent : 1) * 4e-3 * this.get_size();
|
|
2165
|
+
this.svg_element.setAttribute("stroke-width", sw.toString());
|
|
2166
|
+
this.svg_element.setAttribute("stroke-opacity", "1");
|
|
2167
|
+
this.svg_element.setAttribute("stroke", this.__data__.color.toString());
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
draw() {
|
|
2171
|
+
var _a, _b;
|
|
2172
|
+
(_a = this.parent) == null ? void 0 : _a.appendChild(this.svg_element);
|
|
2173
|
+
if (this._arrow_el) {
|
|
2174
|
+
(_b = this.parent) == null ? void 0 : _b.appendChild(this._arrow_el);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
__name(_QELine, "QELine");
|
|
2179
|
+
let QELine = _QELine;
|
|
2180
|
+
const _QEPoint = class _QEPoint extends Drawable {
|
|
2181
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2182
|
+
super(__data__, parent, graph, __children__);
|
|
2183
|
+
this.__data__ = __data__;
|
|
2184
|
+
this.parent = parent;
|
|
2185
|
+
this.graph = graph;
|
|
2186
|
+
this.__children__ = __children__;
|
|
2187
|
+
this.__name__ = "unnamed-point";
|
|
2188
|
+
this.svg_element = this.create();
|
|
2189
|
+
}
|
|
2190
|
+
get x() {
|
|
2191
|
+
return this.__data__.dimensions[this.graph.__data__.options.GRAPH.dimensions[0]];
|
|
2192
|
+
}
|
|
2193
|
+
get y() {
|
|
2194
|
+
return this.__data__.dimensions[this.graph.__data__.options.GRAPH.dimensions[1]];
|
|
2195
|
+
}
|
|
2196
|
+
get isSquare() {
|
|
2197
|
+
return this.__data__.shape === "rect";
|
|
2198
|
+
}
|
|
2199
|
+
/** Half-side length for squares / radius for circles, in SVG user units.
|
|
2200
|
+
* Factor 0.006 keeps code-node diameters in the same visual range as rENA
|
|
2201
|
+
* (≈ 1–2 % of the coordinate half-extent for a typical ENA network).
|
|
2202
|
+
*/
|
|
2203
|
+
computeRadius() {
|
|
2204
|
+
const extent = this.graph.__data__.extremes.x_max;
|
|
2205
|
+
return (extent > 0 ? extent : 1) * 6e-3 * this.__data__.size;
|
|
2206
|
+
}
|
|
2207
|
+
create() {
|
|
2208
|
+
const tag = this.isSquare ? "rect" : "circle";
|
|
2209
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
2210
|
+
el.setAttribute("data-point-id", this.__data__.ID);
|
|
2211
|
+
el.setAttribute("fill", this.__data__.color.toString());
|
|
2212
|
+
const r = this.computeRadius();
|
|
2213
|
+
if (this.isSquare) {
|
|
2214
|
+
el.setAttribute("x", (this.x - r).toString());
|
|
2215
|
+
el.setAttribute("y", (this.y - r).toString());
|
|
2216
|
+
el.setAttribute("width", (r * 2).toString());
|
|
2217
|
+
el.setAttribute("height", (r * 2).toString());
|
|
2218
|
+
} else {
|
|
2219
|
+
el.setAttribute("cx", this.x.toString());
|
|
2220
|
+
el.setAttribute("cy", this.y.toString());
|
|
2221
|
+
el.setAttribute("r", r.toString());
|
|
2222
|
+
}
|
|
2223
|
+
this.svg_element = el;
|
|
2224
|
+
el.addEventListener("click", (event) => {
|
|
2225
|
+
console.log("Point clicked: ", this.__data__);
|
|
2226
|
+
event.stopPropagation();
|
|
2227
|
+
this.__data__.active = !this.__data__.active;
|
|
2228
|
+
this.update();
|
|
2229
|
+
const pointEvent = new CustomEvent("point-clicked", {
|
|
2230
|
+
detail: { pointData: this.__data__ },
|
|
2231
|
+
bubbles: true,
|
|
2232
|
+
composed: true
|
|
2233
|
+
});
|
|
2234
|
+
this.svg_element.dispatchEvent(pointEvent);
|
|
2235
|
+
});
|
|
2236
|
+
el.addEventListener("mouseover", () => {
|
|
2237
|
+
});
|
|
2238
|
+
return el;
|
|
2239
|
+
}
|
|
2240
|
+
update() {
|
|
2241
|
+
if (!this.svg_element) {
|
|
2242
|
+
this.parent.__data__.__children__.push(this.create());
|
|
2243
|
+
}
|
|
2244
|
+
if (!this.svg_element.parentElement) {
|
|
2245
|
+
this.parent.appendChild(this.svg_element);
|
|
2246
|
+
}
|
|
2247
|
+
const r = this.computeRadius();
|
|
2248
|
+
this.svg_element.setAttribute("fill", this.__data__.color.toString());
|
|
2249
|
+
if (this.isSquare) {
|
|
2250
|
+
this.svg_element.setAttribute("x", (this.x - r).toString());
|
|
2251
|
+
this.svg_element.setAttribute("y", (this.y - r).toString());
|
|
2252
|
+
this.svg_element.setAttribute("width", (r * 2).toString());
|
|
2253
|
+
this.svg_element.setAttribute("height", (r * 2).toString());
|
|
2254
|
+
} else {
|
|
2255
|
+
this.svg_element.setAttribute("cx", this.x.toString());
|
|
2256
|
+
this.svg_element.setAttribute("cy", this.y.toString());
|
|
2257
|
+
this.svg_element.setAttribute("r", r.toString());
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
draw() {
|
|
2261
|
+
var _a;
|
|
2262
|
+
(_a = this.parent) == null ? void 0 : _a.appendChild(this.svg_element);
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
__name(_QEPoint, "QEPoint");
|
|
2266
|
+
let QEPoint = _QEPoint;
|
|
2267
|
+
const { ArrayTools } = Tools;
|
|
2268
|
+
const _QEArray = class _QEArray extends Array {
|
|
2269
|
+
constructor(...args) {
|
|
2270
|
+
super(...args);
|
|
2271
|
+
}
|
|
2272
|
+
search(key, name) {
|
|
2273
|
+
let found;
|
|
2274
|
+
function fn(a) {
|
|
2275
|
+
return a.name === name ? a : void 0;
|
|
2276
|
+
}
|
|
2277
|
+
__name(fn, "fn");
|
|
2278
|
+
function recurse(obj, i, parent = null) {
|
|
2279
|
+
if (ArrayTools.isArray(obj)) {
|
|
2280
|
+
for (const [i2, o] of obj.entries()) {
|
|
2281
|
+
found = recurse(o, i2, parent);
|
|
2282
|
+
if (found) {
|
|
2283
|
+
return found;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
} else {
|
|
2287
|
+
found = fn(obj);
|
|
2288
|
+
if (!found) {
|
|
2289
|
+
if (obj && ArrayTools.isArray(obj[key])) {
|
|
2290
|
+
for (const [i2, o] of obj[key].entries()) {
|
|
2291
|
+
found = recurse(o, i2, obj);
|
|
2292
|
+
if (found) {
|
|
2293
|
+
return found;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
} else {
|
|
2298
|
+
return found;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return found;
|
|
2302
|
+
}
|
|
2303
|
+
__name(recurse, "recurse");
|
|
2304
|
+
return recurse(this);
|
|
2305
|
+
}
|
|
2306
|
+
flatten() {
|
|
2307
|
+
return this.reduce((a, l) => {
|
|
2308
|
+
a.push(l);
|
|
2309
|
+
if (l.layers && Array.isArray(l.layers)) {
|
|
2310
|
+
a.push(...l.layers);
|
|
2311
|
+
}
|
|
2312
|
+
return a;
|
|
2313
|
+
}, []);
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
__name(_QEArray, "QEArray");
|
|
2317
|
+
let QEArray = _QEArray;
|
|
2318
|
+
const QEElementConstructors = {
|
|
2319
|
+
"AxisPoint": QEPoint,
|
|
2320
|
+
"AxisLine": QELine,
|
|
2321
|
+
"Point": QEPoint,
|
|
2322
|
+
"Line": QELine,
|
|
2323
|
+
"Edge": QELine,
|
|
2324
|
+
"TaperedEdge": QELine,
|
|
2325
|
+
"TaperedWeightedMidpointEdge": QELine,
|
|
2326
|
+
"NodeEdge": QELine
|
|
2327
|
+
};
|
|
2328
|
+
const _QELayer = class _QELayer {
|
|
2329
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2330
|
+
this.__data__ = __data__;
|
|
2331
|
+
this.parent = parent;
|
|
2332
|
+
this.graph = graph;
|
|
2333
|
+
this.__children__ = __children__;
|
|
2334
|
+
this.__name__ = __data__.name || "unnamed-layer";
|
|
2335
|
+
__data__.__state__.addListener(`layer_updated`, "live", ({ key, newValue, oldValue }) => {
|
|
2336
|
+
const childId = /* @__PURE__ */ __name((child) => {
|
|
2337
|
+
var _a;
|
|
2338
|
+
return (_a = child.__data__) == null ? void 0 : _a.ID;
|
|
2339
|
+
}, "childId");
|
|
2340
|
+
const removing = this.__children__.filter(
|
|
2341
|
+
(child) => !newValue.objects.has(childId(child))
|
|
2342
|
+
);
|
|
2343
|
+
const updating = this.__children__.filter(
|
|
2344
|
+
(child) => newValue.objects.has(childId(child))
|
|
2345
|
+
);
|
|
2346
|
+
const adding = [...newValue.objects.values()].filter(
|
|
2347
|
+
(obj) => !this.__children__.some((child) => childId(child) === obj.ID)
|
|
2348
|
+
);
|
|
2349
|
+
adding.forEach((obj) => {
|
|
2350
|
+
const obj_name = obj.constructor.name;
|
|
2351
|
+
const ena_element = new QEElementConstructors[obj_name](obj, this.svg_element, this.graph);
|
|
2352
|
+
this.__children__.push(ena_element);
|
|
2353
|
+
});
|
|
2354
|
+
updating.forEach((child) => {
|
|
2355
|
+
const newData = newValue.objects.get(childId(child));
|
|
2356
|
+
if (newData) child.__data__ = newData;
|
|
2357
|
+
child.update();
|
|
2358
|
+
});
|
|
2359
|
+
removing.forEach((child) => {
|
|
2360
|
+
if (child.svg_element.parentElement) {
|
|
2361
|
+
child.svg_element.parentElement.removeChild(child.svg_element);
|
|
2362
|
+
}
|
|
2363
|
+
const idx = this.__children__.indexOf(child);
|
|
2364
|
+
if (idx >= 0) this.__children__.splice(idx, 1);
|
|
2365
|
+
});
|
|
2366
|
+
this.update();
|
|
2367
|
+
});
|
|
2368
|
+
this.svg_element = this.create();
|
|
2369
|
+
}
|
|
2370
|
+
get name() {
|
|
2371
|
+
return this.__name__;
|
|
2372
|
+
}
|
|
2373
|
+
create() {
|
|
2374
|
+
if (!this.__data__) {
|
|
2375
|
+
throw new Error("Layer is null");
|
|
2376
|
+
}
|
|
2377
|
+
let layer = this.__data__;
|
|
2378
|
+
this.svg_element = document.createElementNS("http://www.w3.org/2000/svg", _QELayer.tagName);
|
|
2379
|
+
this.svg_element.setAttribute("data-layer-name", layer.name);
|
|
2380
|
+
this.svg_element.setAttribute("data-layer-id", layer.ID);
|
|
2381
|
+
this.svg_element.__data__ = layer;
|
|
2382
|
+
this.svg_element.classList.add("ena-layer");
|
|
2383
|
+
if (this.__data__.layers && this.__data__.layers.length > 0) {
|
|
2384
|
+
this.__data__.layers.forEach((sublayer) => {
|
|
2385
|
+
const layer_g = new _QELayer(sublayer, this.svg_element, this.graph);
|
|
2386
|
+
this.__children__.push(layer_g);
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
return this.svg_element;
|
|
2390
|
+
}
|
|
2391
|
+
update() {
|
|
2392
|
+
if (!this.__data__) {
|
|
2393
|
+
throw new Error("Layer is null");
|
|
2394
|
+
}
|
|
2395
|
+
if (this.__data__.visible) {
|
|
2396
|
+
if (this.parent.nodeName.toLowerCase() === "svg") ;
|
|
2397
|
+
this.__children__.forEach((child) => {
|
|
2398
|
+
child.update();
|
|
2399
|
+
});
|
|
2400
|
+
if (this.__data__.scale_objects === true) {
|
|
2401
|
+
if (this.__name__ === "points") ;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
draw() {
|
|
2406
|
+
var _a;
|
|
2407
|
+
(_a = this.parent) == null ? void 0 : _a.appendChild(this.svg_element);
|
|
2408
|
+
this.__children__.forEach((child) => {
|
|
2409
|
+
child.draw();
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2412
|
+
add(child) {
|
|
2413
|
+
this.__children__.push(child);
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
__name(_QELayer, "QELayer");
|
|
2417
|
+
_QELayer.tagName = "g";
|
|
2418
|
+
let QELayer = _QELayer;
|
|
2419
|
+
const _QELabel = class _QELabel {
|
|
2420
|
+
constructor(text, point, parent, graph, displayMode = Label.DISPLAY_TYPES.ON, position = Label.POSITION.OBJECT, customX, customY) {
|
|
2421
|
+
this.text = text;
|
|
2422
|
+
this.point = point;
|
|
2423
|
+
this.parent = parent;
|
|
2424
|
+
this.graph = graph;
|
|
2425
|
+
this.displayMode = displayMode;
|
|
2426
|
+
this.position = position;
|
|
2427
|
+
this.customX = customX;
|
|
2428
|
+
this.customY = customY;
|
|
2429
|
+
this._visible = false;
|
|
2430
|
+
this.svg_element = this._create();
|
|
2431
|
+
if (displayMode === Label.DISPLAY_TYPES.ON) {
|
|
2432
|
+
this._visible = true;
|
|
2433
|
+
this.svg_element.setAttribute("display", "block");
|
|
2434
|
+
} else {
|
|
2435
|
+
this._visible = false;
|
|
2436
|
+
this.svg_element.setAttribute("display", "none");
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
_create() {
|
|
2440
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
2441
|
+
el.textContent = this.text;
|
|
2442
|
+
el.setAttribute("data-label-for", this.point.ID);
|
|
2443
|
+
el.setAttribute("pointer-events", "none");
|
|
2444
|
+
this.parent.appendChild(el);
|
|
2445
|
+
return el;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Recompute position and font-size from current graph extents.
|
|
2449
|
+
* No-ops when the label is hidden (AUTO/CLICK modes while not hovered/clicked).
|
|
2450
|
+
*/
|
|
2451
|
+
update() {
|
|
2452
|
+
var _a;
|
|
2453
|
+
if (!this._visible) return;
|
|
2454
|
+
const extent = this.graph.__data__.extremes.x_max;
|
|
2455
|
+
if (extent <= 0) return;
|
|
2456
|
+
const fontSize = extent * 0.06;
|
|
2457
|
+
const offset = fontSize * 1.2;
|
|
2458
|
+
let x, y, anchor;
|
|
2459
|
+
switch (this.position) {
|
|
2460
|
+
case Label.POSITION.STATIC: {
|
|
2461
|
+
x = this.point.x;
|
|
2462
|
+
y = this.point.y - offset * 0.3;
|
|
2463
|
+
anchor = "middle";
|
|
2464
|
+
break;
|
|
2465
|
+
}
|
|
2466
|
+
case Label.POSITION.CONTAINER: {
|
|
2467
|
+
const px = this.point.x;
|
|
2468
|
+
const py = this.point.y;
|
|
2469
|
+
const dist = Math.sqrt(px * px + py * py) || 1;
|
|
2470
|
+
const scale = extent * 0.93 / dist;
|
|
2471
|
+
x = px * scale;
|
|
2472
|
+
y = py * scale - offset * 0.3;
|
|
2473
|
+
anchor = px >= 0 ? "start" : "end";
|
|
2474
|
+
break;
|
|
2475
|
+
}
|
|
2476
|
+
case Label.POSITION.CUSTOM: {
|
|
2477
|
+
if (this.customX != null && this.customY != null) {
|
|
2478
|
+
x = this.customX;
|
|
2479
|
+
y = this.customY;
|
|
2480
|
+
anchor = "start";
|
|
2481
|
+
break;
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
default:
|
|
2485
|
+
case Label.POSITION.OBJECT: {
|
|
2486
|
+
const dx = this.point.x >= 0 ? offset : -offset;
|
|
2487
|
+
anchor = this.point.x >= 0 ? "start" : "end";
|
|
2488
|
+
x = this.point.x + dx;
|
|
2489
|
+
y = this.point.y - offset * 0.4;
|
|
2490
|
+
break;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
this.svg_element.setAttribute("x", x.toString());
|
|
2494
|
+
this.svg_element.setAttribute("y", y.toString());
|
|
2495
|
+
this.svg_element.setAttribute("font-size", fontSize.toString());
|
|
2496
|
+
this.svg_element.setAttribute("font-family", "system-ui, sans-serif");
|
|
2497
|
+
this.svg_element.setAttribute("font-weight", "500");
|
|
2498
|
+
this.svg_element.setAttribute("text-anchor", anchor);
|
|
2499
|
+
this.svg_element.setAttribute("fill", ((_a = this.point.color) == null ? void 0 : _a.toString()) ?? "#333333");
|
|
2500
|
+
}
|
|
2501
|
+
/** Make the label visible and position it immediately. */
|
|
2502
|
+
show() {
|
|
2503
|
+
this._visible = true;
|
|
2504
|
+
this.svg_element.setAttribute("display", "block");
|
|
2505
|
+
this.update();
|
|
2506
|
+
}
|
|
2507
|
+
hide() {
|
|
2508
|
+
this._visible = false;
|
|
2509
|
+
this.svg_element.setAttribute("display", "none");
|
|
2510
|
+
}
|
|
2511
|
+
toggle() {
|
|
2512
|
+
this._visible ? this.hide() : this.show();
|
|
2513
|
+
}
|
|
2514
|
+
get visible() {
|
|
2515
|
+
return this._visible;
|
|
2516
|
+
}
|
|
2517
|
+
remove() {
|
|
2518
|
+
this.svg_element.remove();
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
__name(_QELabel, "QELabel");
|
|
2522
|
+
let QELabel = _QELabel;
|
|
2523
|
+
const _QENodesElement = class _QENodesElement extends HTMLElement {
|
|
2524
|
+
};
|
|
2525
|
+
__name(_QENodesElement, "QENodesElement");
|
|
2526
|
+
_QENodesElement.tagName = "qe-nodes";
|
|
2527
|
+
let QENodesElement = _QENodesElement;
|
|
2528
|
+
const _QEMeansElement = class _QEMeansElement extends HTMLElement {
|
|
2529
|
+
};
|
|
2530
|
+
__name(_QEMeansElement, "QEMeansElement");
|
|
2531
|
+
_QEMeansElement.tagName = "qe-means";
|
|
2532
|
+
let QEMeansElement = _QEMeansElement;
|
|
2533
|
+
const _QEPointsElement = class _QEPointsElement extends HTMLElement {
|
|
2534
|
+
};
|
|
2535
|
+
__name(_QEPointsElement, "QEPointsElement");
|
|
2536
|
+
_QEPointsElement.tagName = "qe-points";
|
|
2537
|
+
let QEPointsElement = _QEPointsElement;
|
|
2538
|
+
const _QEEdgesElement = class _QEEdgesElement extends HTMLElement {
|
|
2539
|
+
};
|
|
2540
|
+
__name(_QEEdgesElement, "QEEdgesElement");
|
|
2541
|
+
_QEEdgesElement.tagName = "qe-edges";
|
|
2542
|
+
let QEEdgesElement = _QEEdgesElement;
|
|
2543
|
+
customElements.define(QENodesElement.tagName, QENodesElement);
|
|
2544
|
+
customElements.define(QEMeansElement.tagName, QEMeansElement);
|
|
2545
|
+
customElements.define(QEPointsElement.tagName, QEPointsElement);
|
|
2546
|
+
customElements.define(QEEdgesElement.tagName, QEEdgesElement);
|
|
2547
|
+
const GROUP_PALETTE = [
|
|
2548
|
+
"#4477AA",
|
|
2549
|
+
"#EE6677",
|
|
2550
|
+
"#228833",
|
|
2551
|
+
"#CCBB44",
|
|
2552
|
+
"#66CCEE",
|
|
2553
|
+
"#AA3377",
|
|
2554
|
+
"#BBBBBB",
|
|
2555
|
+
"#000000"
|
|
2556
|
+
];
|
|
2557
|
+
function parseEdgePair(colName, codes) {
|
|
2558
|
+
for (const c of codes) {
|
|
2559
|
+
if (colName === `${c}.${c}`) return [c, c];
|
|
2560
|
+
}
|
|
2561
|
+
for (const c1 of codes) {
|
|
2562
|
+
if (colName.startsWith(c1 + ".")) {
|
|
2563
|
+
const c2 = colName.slice(c1.length + 1);
|
|
2564
|
+
if (codes.includes(c2)) return [c1, c2];
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
__name(parseEdgePair, "parseEdgePair");
|
|
2570
|
+
function computeCIRect(varX, varY, n, pointScale) {
|
|
2571
|
+
const df = Math.max(1, n - 1);
|
|
2572
|
+
const t95 = df >= 120 ? 1.96 : df >= 60 ? 2 : df >= 30 ? 2.042 : df >= 20 ? 2.086 : df >= 10 ? 2.228 : df >= 5 ? 2.571 : 4.303;
|
|
2573
|
+
const seX = Math.sqrt(Math.max(0, varX) / n) * pointScale;
|
|
2574
|
+
const seY = Math.sqrt(Math.max(0, varY) / n) * pointScale;
|
|
2575
|
+
return { halfW: t95 * seX, halfH: t95 * seY };
|
|
2576
|
+
}
|
|
2577
|
+
__name(computeCIRect, "computeCIRect");
|
|
2578
|
+
function readIntervalBounds(row, dim0, dim1) {
|
|
2579
|
+
const get = /* @__PURE__ */ __name((col) => {
|
|
2580
|
+
const i = row.names.indexOf(col);
|
|
2581
|
+
return i >= 0 ? Number(row[i]) : void 0;
|
|
2582
|
+
}, "get");
|
|
2583
|
+
const xLow = get(`${dim0}.low`) ?? get(`${dim0}_low`);
|
|
2584
|
+
const xHigh = get(`${dim0}.high`) ?? get(`${dim0}_high`);
|
|
2585
|
+
const yLow = get(`${dim1}.low`) ?? get(`${dim1}_low`);
|
|
2586
|
+
const yHigh = get(`${dim1}.high`) ?? get(`${dim1}_high`);
|
|
2587
|
+
if (xLow == null || xHigh == null || yLow == null || yHigh == null) return null;
|
|
2588
|
+
return { x_low: xLow, x_high: xHigh, y_low: yLow, y_high: yHigh };
|
|
2589
|
+
}
|
|
2590
|
+
__name(readIntervalBounds, "readIntervalBounds");
|
|
2591
|
+
function parseLabelMode(attr, defaultMode) {
|
|
2592
|
+
switch (attr == null ? void 0 : attr.toLowerCase()) {
|
|
2593
|
+
case "on":
|
|
2594
|
+
return 3;
|
|
2595
|
+
case "off":
|
|
2596
|
+
return 0;
|
|
2597
|
+
case "click":
|
|
2598
|
+
return 2;
|
|
2599
|
+
case "auto":
|
|
2600
|
+
return 1;
|
|
2601
|
+
default:
|
|
2602
|
+
return defaultMode;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
__name(parseLabelMode, "parseLabelMode");
|
|
2606
|
+
const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
2607
|
+
constructor() {
|
|
2608
|
+
super();
|
|
2609
|
+
this.zoom_level = 1;
|
|
2610
|
+
this.__children__ = new QEArray();
|
|
2611
|
+
this._hasModel = false;
|
|
2612
|
+
this._renderPending = false;
|
|
2613
|
+
this._childObserver = null;
|
|
2614
|
+
this._ciRects = /* @__PURE__ */ new Map();
|
|
2615
|
+
this._outlierRects = /* @__PURE__ */ new Map();
|
|
2616
|
+
this._labelsGroup = null;
|
|
2617
|
+
this._nodeLabels = /* @__PURE__ */ new Map();
|
|
2618
|
+
this._meanLabels = /* @__PURE__ */ new Map();
|
|
2619
|
+
this._pointLabels = /* @__PURE__ */ new Map();
|
|
2620
|
+
this.graph = this;
|
|
2621
|
+
this.parent = null;
|
|
2622
|
+
this.__data__ = new Graph(DefaultGraphOptions);
|
|
2623
|
+
this.__name__ = this.__data__.name || "unnamed-graph";
|
|
2624
|
+
this.svg_element = this.create();
|
|
2625
|
+
console.log("Created ena-graph svg element: ", this.svg_element);
|
|
2626
|
+
document.addEventListener("model-updated", (e) => {
|
|
2627
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
2628
|
+
if (this._vizena && e.target !== this._vizena) return;
|
|
2629
|
+
const model = e.detail;
|
|
2630
|
+
if (!model) return;
|
|
2631
|
+
this._hasModel = true;
|
|
2632
|
+
const dims = model.dimensions;
|
|
2633
|
+
const unitIdCol = model.unit_id_col;
|
|
2634
|
+
const nodeIdCol = model.node_id_col;
|
|
2635
|
+
const groupColName = model.group_col;
|
|
2636
|
+
const config = this._readLayerConfig();
|
|
2637
|
+
const groupColorMap = {};
|
|
2638
|
+
if (groupColName && ((_a = model.points) == null ? void 0 : _a.names)) {
|
|
2639
|
+
const groupCol = model.points.col(groupColName);
|
|
2640
|
+
if (groupCol) {
|
|
2641
|
+
const uniqueGroups = [...new Set([...groupCol].map(String))];
|
|
2642
|
+
uniqueGroups.forEach((g, i) => {
|
|
2643
|
+
groupColorMap[g] = GROUP_PALETTE[i % GROUP_PALETTE.length];
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
} else if (((_b = model.groups) == null ? void 0 : _b.rows) && model.groups.rows().length > 0) {
|
|
2647
|
+
model.groups.rows().forEach((row, i) => {
|
|
2648
|
+
const g = row.rowName ?? String(row[0]);
|
|
2649
|
+
if (!groupColorMap[g]) groupColorMap[g] = GROUP_PALETTE[i % GROUP_PALETTE.length];
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
const _allGroupNames_ = Object.keys(groupColorMap);
|
|
2653
|
+
const activeMeanGroups = config.activeMeanGroups && config.activeMeanGroups.trim() ? new Set(
|
|
2654
|
+
config.activeMeanGroups.split(",").map((s) => s.trim()).filter((g) => groupColorMap[g])
|
|
2655
|
+
// silently drop names not in model
|
|
2656
|
+
) : new Set(_allGroupNames_);
|
|
2657
|
+
const scalePointsEnabled = config.scalePoints;
|
|
2658
|
+
const getExtent = /* @__PURE__ */ __name((rows) => {
|
|
2659
|
+
let max = 0;
|
|
2660
|
+
rows.forEach((row) => {
|
|
2661
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
2662
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
2663
|
+
if (xIdx >= 0) max = Math.max(max, Math.abs(Number(row[xIdx])));
|
|
2664
|
+
if (yIdx >= 0) max = Math.max(max, Math.abs(Number(row[yIdx])));
|
|
2665
|
+
});
|
|
2666
|
+
return max;
|
|
2667
|
+
}, "getExtent");
|
|
2668
|
+
const maxNodeExtent = ((_c = model.nodes) == null ? void 0 : _c.rows) ? getExtent(model.nodes.rows()) : 0;
|
|
2669
|
+
const maxPointExtent = ((_d = model.points) == null ? void 0 : _d.rows) ? getExtent(model.points.rows()) : 0;
|
|
2670
|
+
const pointScale = scalePointsEnabled && maxPointExtent > 0 && maxNodeExtent > 0 ? maxNodeExtent / maxPointExtent : 1;
|
|
2671
|
+
const labelNodes = config.labelNodes;
|
|
2672
|
+
const labelMeans = config.labelMeans;
|
|
2673
|
+
const labelPoints = config.labelPoints;
|
|
2674
|
+
const labelSvg = this._labelsGroup;
|
|
2675
|
+
const rowToPoint = /* @__PURE__ */ __name((row, idCol, colorStr, scale = 1) => {
|
|
2676
|
+
const idIdx = row.names.indexOf(idCol);
|
|
2677
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
2678
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
2679
|
+
const pt = new Point({
|
|
2680
|
+
ID: idIdx >= 0 ? String(row[idIdx]) : row.rowName || `pt-${Math.random().toString(36).slice(2)}`,
|
|
2681
|
+
dimensions: [
|
|
2682
|
+
xIdx >= 0 ? Number(row[xIdx]) * scale : 0,
|
|
2683
|
+
yIdx >= 0 ? -Number(row[yIdx]) * scale : 0
|
|
2684
|
+
// negate Y: SVG y-down vs ENA y-up
|
|
2685
|
+
],
|
|
2686
|
+
meta: {}
|
|
2687
|
+
});
|
|
2688
|
+
if (colorStr) pt.color = new Color(colorStr);
|
|
2689
|
+
return pt;
|
|
2690
|
+
}, "rowToPoint");
|
|
2691
|
+
const groupSums = {};
|
|
2692
|
+
const showUnitPoints = config.showPoints;
|
|
2693
|
+
if ((_e = model.points) == null ? void 0 : _e.rows) {
|
|
2694
|
+
const pointsLayer = this.__children__.search("__children__", "points");
|
|
2695
|
+
if (pointsLayer) {
|
|
2696
|
+
model.points.rows().forEach((row) => {
|
|
2697
|
+
const groupColIdx = groupColName ? row.names.indexOf(groupColName) : -1;
|
|
2698
|
+
const group = groupColIdx >= 0 ? String(row[groupColIdx]) : null;
|
|
2699
|
+
const colorStr = group ? groupColorMap[group] ?? "#888888" : "#888888";
|
|
2700
|
+
if (showUnitPoints) {
|
|
2701
|
+
const pt = rowToPoint(row, unitIdCol, colorStr, pointScale);
|
|
2702
|
+
this.__data__.points.set(pt.ID, pt);
|
|
2703
|
+
pointsLayer.__data__.objects.set(pt.ID, pt);
|
|
2704
|
+
}
|
|
2705
|
+
if (group) {
|
|
2706
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
2707
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
2708
|
+
if (!groupSums[group]) groupSums[group] = { x: 0, y: 0, x2: 0, y2: 0, xy: 0, n: 0, color: colorStr };
|
|
2709
|
+
const rx = xIdx >= 0 ? Number(row[xIdx]) : 0;
|
|
2710
|
+
const ry = yIdx >= 0 ? Number(row[yIdx]) : 0;
|
|
2711
|
+
groupSums[group].x += rx;
|
|
2712
|
+
groupSums[group].y += ry;
|
|
2713
|
+
groupSums[group].x2 += rx * rx;
|
|
2714
|
+
groupSums[group].y2 += ry * ry;
|
|
2715
|
+
groupSums[group].xy += rx * ry;
|
|
2716
|
+
groupSums[group].n += 1;
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
const hasGroupsFrame = !!(((_f = model.groups) == null ? void 0 : _f.rows) && model.groups.rows().length > 0);
|
|
2720
|
+
const activeGroupRows = config.showMeans && hasGroupsFrame ? model.groups.rows().filter((r) => {
|
|
2721
|
+
const g = r.rowName ?? String(r[0]);
|
|
2722
|
+
return activeMeanGroups.size === 0 || activeMeanGroups.has(g);
|
|
2723
|
+
}) : [];
|
|
2724
|
+
if (config.showMeans) {
|
|
2725
|
+
const pointsLayerSvg = pointsLayer.svg_element;
|
|
2726
|
+
const sw = (maxNodeExtent > 0 ? maxNodeExtent : 1) * 8e-3;
|
|
2727
|
+
if (hasGroupsFrame) {
|
|
2728
|
+
const activeNames = new Set(activeGroupRows.map((r) => r.rowName ?? String(r[0])));
|
|
2729
|
+
this._ciRects.forEach((el, g) => {
|
|
2730
|
+
if (!activeNames.has(g)) {
|
|
2731
|
+
el.remove();
|
|
2732
|
+
this._ciRects.delete(g);
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
this._outlierRects.forEach((el, g) => {
|
|
2736
|
+
if (!activeNames.has(g)) {
|
|
2737
|
+
el.remove();
|
|
2738
|
+
this._outlierRects.delete(g);
|
|
2739
|
+
}
|
|
2740
|
+
});
|
|
2741
|
+
activeGroupRows.forEach((row) => {
|
|
2742
|
+
const group = row.rowName ?? String(row[0]);
|
|
2743
|
+
const color = groupColorMap[group] ?? "#888888";
|
|
2744
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
2745
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
2746
|
+
const mx = xIdx >= 0 ? Number(row[xIdx]) * pointScale : 0;
|
|
2747
|
+
const my = yIdx >= 0 ? -Number(row[yIdx]) * pointScale : 0;
|
|
2748
|
+
const meanPt = new Point({
|
|
2749
|
+
ID: `mean-${group}`,
|
|
2750
|
+
dimensions: [mx, my],
|
|
2751
|
+
meta: { group }
|
|
2752
|
+
});
|
|
2753
|
+
meanPt.color = new Color(color);
|
|
2754
|
+
meanPt.size = 4;
|
|
2755
|
+
meanPt.shape = "rect";
|
|
2756
|
+
this.__data__.points.set(meanPt.ID, meanPt);
|
|
2757
|
+
pointsLayer.__data__.objects.set(meanPt.ID, meanPt);
|
|
2758
|
+
const bounds = readIntervalBounds(row, dims[0], dims[1]);
|
|
2759
|
+
if (bounds) {
|
|
2760
|
+
const rect_x = bounds.x_low * pointScale;
|
|
2761
|
+
const rect_y = -bounds.y_high * pointScale;
|
|
2762
|
+
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
2763
|
+
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
2764
|
+
let el = this._ciRects.get(group);
|
|
2765
|
+
if (!el) {
|
|
2766
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
2767
|
+
el.setAttribute("data-ci-group", group);
|
|
2768
|
+
this._ciRects.set(group, el);
|
|
2769
|
+
}
|
|
2770
|
+
el.setAttribute("x", rect_x.toString());
|
|
2771
|
+
el.setAttribute("y", rect_y.toString());
|
|
2772
|
+
el.setAttribute("width", rect_width.toString());
|
|
2773
|
+
el.setAttribute("height", rect_height.toString());
|
|
2774
|
+
el.setAttribute("fill", "none");
|
|
2775
|
+
el.setAttribute("stroke", color);
|
|
2776
|
+
el.setAttribute("stroke-width", sw.toString());
|
|
2777
|
+
el.setAttribute("stroke-opacity", "0.8");
|
|
2778
|
+
el.setAttribute("stroke-dasharray", `${sw * 4} ${sw * 3}`);
|
|
2779
|
+
if (pointsLayerSvg && !el.parentElement) {
|
|
2780
|
+
pointsLayerSvg.insertBefore(el, pointsLayerSvg.firstChild);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
} else {
|
|
2785
|
+
Object.entries(groupSums).forEach(([group, { x, y, n, color }]) => {
|
|
2786
|
+
const meanPt = new Point({
|
|
2787
|
+
ID: `mean-${group}`,
|
|
2788
|
+
dimensions: [x / n * pointScale, -(y / n) * pointScale],
|
|
2789
|
+
meta: { group }
|
|
2790
|
+
});
|
|
2791
|
+
meanPt.color = new Color(color);
|
|
2792
|
+
meanPt.size = 4;
|
|
2793
|
+
meanPt.shape = "rect";
|
|
2794
|
+
this.__data__.points.set(meanPt.ID, meanPt);
|
|
2795
|
+
pointsLayer.__data__.objects.set(meanPt.ID, meanPt);
|
|
2796
|
+
});
|
|
2797
|
+
const showCI = config.showCI;
|
|
2798
|
+
if (showCI) {
|
|
2799
|
+
this._ciRects.forEach((el, g) => {
|
|
2800
|
+
if (!groupSums[g]) {
|
|
2801
|
+
el.remove();
|
|
2802
|
+
this._ciRects.delete(g);
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
if ((_g = model.confidence) == null ? void 0 : _g.rows) {
|
|
2806
|
+
model.confidence.rows().forEach((row) => {
|
|
2807
|
+
const groupIdx = row.names.indexOf(groupColName ?? "group");
|
|
2808
|
+
const group = groupIdx >= 0 ? String(row[groupIdx]) : row.rowName ?? null;
|
|
2809
|
+
if (!group) return;
|
|
2810
|
+
const color = groupColorMap[group] ?? "#888888";
|
|
2811
|
+
const bounds = readIntervalBounds(row, dims[0], dims[1]);
|
|
2812
|
+
if (!bounds) return;
|
|
2813
|
+
const rect_x = bounds.x_low * pointScale;
|
|
2814
|
+
const rect_y = -bounds.y_high * pointScale;
|
|
2815
|
+
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
2816
|
+
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
2817
|
+
let el = this._ciRects.get(group);
|
|
2818
|
+
if (!el) {
|
|
2819
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
2820
|
+
el.setAttribute("data-ci-group", group);
|
|
2821
|
+
this._ciRects.set(group, el);
|
|
2822
|
+
}
|
|
2823
|
+
el.setAttribute("x", rect_x.toString());
|
|
2824
|
+
el.setAttribute("y", rect_y.toString());
|
|
2825
|
+
el.setAttribute("width", rect_width.toString());
|
|
2826
|
+
el.setAttribute("height", rect_height.toString());
|
|
2827
|
+
el.setAttribute("fill", "none");
|
|
2828
|
+
el.setAttribute("stroke", color);
|
|
2829
|
+
el.setAttribute("stroke-width", sw.toString());
|
|
2830
|
+
el.setAttribute("stroke-opacity", "0.8");
|
|
2831
|
+
el.setAttribute("stroke-dasharray", `${sw * 4} ${sw * 3}`);
|
|
2832
|
+
if (pointsLayerSvg && !el.parentElement) {
|
|
2833
|
+
pointsLayerSvg.insertBefore(el, pointsLayerSvg.firstChild);
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
} else {
|
|
2837
|
+
Object.entries(groupSums).forEach(([group, { x, y, x2, y2, n, color }]) => {
|
|
2838
|
+
if (n < 2) return;
|
|
2839
|
+
const μx = x / n, μy = y / n;
|
|
2840
|
+
const rawVarX = x2 / n - μx * μx;
|
|
2841
|
+
const rawVarY = y2 / n - μy * μy;
|
|
2842
|
+
const { halfW, halfH } = computeCIRect(rawVarX, rawVarY, n, pointScale);
|
|
2843
|
+
const cx = μx * pointScale, cy = -μy * pointScale;
|
|
2844
|
+
let el = this._ciRects.get(group);
|
|
2845
|
+
if (!el) {
|
|
2846
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
2847
|
+
el.setAttribute("data-ci-group", group);
|
|
2848
|
+
this._ciRects.set(group, el);
|
|
2849
|
+
}
|
|
2850
|
+
el.setAttribute("x", (cx - halfW).toString());
|
|
2851
|
+
el.setAttribute("y", (cy - halfH).toString());
|
|
2852
|
+
el.setAttribute("width", (halfW * 2).toString());
|
|
2853
|
+
el.setAttribute("height", (halfH * 2).toString());
|
|
2854
|
+
el.setAttribute("fill", "none");
|
|
2855
|
+
el.setAttribute("stroke", color);
|
|
2856
|
+
el.setAttribute("stroke-width", sw.toString());
|
|
2857
|
+
el.setAttribute("stroke-opacity", "0.8");
|
|
2858
|
+
el.setAttribute("stroke-dasharray", `${sw * 4} ${sw * 3}`);
|
|
2859
|
+
if (pointsLayerSvg && !el.parentElement) {
|
|
2860
|
+
pointsLayerSvg.insertBefore(el, pointsLayerSvg.firstChild);
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if ((_h = model.outlier) == null ? void 0 : _h.rows) {
|
|
2866
|
+
this._outlierRects.forEach((el, g) => {
|
|
2867
|
+
if (!groupColorMap[g]) {
|
|
2868
|
+
el.remove();
|
|
2869
|
+
this._outlierRects.delete(g);
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
model.outlier.rows().forEach((row) => {
|
|
2873
|
+
const groupIdx = row.names.indexOf(groupColName ?? "group");
|
|
2874
|
+
const group = groupIdx >= 0 ? String(row[groupIdx]) : row.rowName ?? null;
|
|
2875
|
+
if (!group) return;
|
|
2876
|
+
const color = groupColorMap[group] ?? "#888888";
|
|
2877
|
+
const bounds = readIntervalBounds(row, dims[0], dims[1]);
|
|
2878
|
+
if (!bounds) return;
|
|
2879
|
+
const rect_x = bounds.x_low * pointScale;
|
|
2880
|
+
const rect_y = -bounds.y_high * pointScale;
|
|
2881
|
+
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
2882
|
+
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
2883
|
+
let el = this._outlierRects.get(group);
|
|
2884
|
+
if (!el) {
|
|
2885
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
2886
|
+
el.setAttribute("data-outlier-group", group);
|
|
2887
|
+
this._outlierRects.set(group, el);
|
|
2888
|
+
}
|
|
2889
|
+
el.setAttribute("x", rect_x.toString());
|
|
2890
|
+
el.setAttribute("y", rect_y.toString());
|
|
2891
|
+
el.setAttribute("width", rect_width.toString());
|
|
2892
|
+
el.setAttribute("height", rect_height.toString());
|
|
2893
|
+
el.setAttribute("fill", "none");
|
|
2894
|
+
el.setAttribute("stroke", color);
|
|
2895
|
+
el.setAttribute("stroke-width", sw.toString());
|
|
2896
|
+
el.setAttribute("stroke-opacity", "0.5");
|
|
2897
|
+
el.setAttribute("stroke-dasharray", `${sw * 2} ${sw * 2}`);
|
|
2898
|
+
if (pointsLayerSvg && !el.parentElement) {
|
|
2899
|
+
pointsLayerSvg.insertBefore(el, pointsLayerSvg.firstChild);
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
if (labelSvg) {
|
|
2906
|
+
this._pointLabels.forEach((lbl, id) => {
|
|
2907
|
+
if (!pointsLayer.__data__.objects.get(id)) {
|
|
2908
|
+
lbl.remove();
|
|
2909
|
+
this._pointLabels.delete(id);
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2912
|
+
this._meanLabels.forEach((lbl, id) => {
|
|
2913
|
+
if (!pointsLayer.__data__.objects.get(`mean-${id}`)) {
|
|
2914
|
+
lbl.remove();
|
|
2915
|
+
this._meanLabels.delete(id);
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
if (labelPoints !== 0) {
|
|
2919
|
+
model.points.rows().forEach((row) => {
|
|
2920
|
+
const qIdx = row.names.indexOf(unitIdCol);
|
|
2921
|
+
const unitId = qIdx >= 0 ? String(row[qIdx]) : null;
|
|
2922
|
+
if (!unitId) return;
|
|
2923
|
+
const pt = pointsLayer.__data__.objects.get(unitId);
|
|
2924
|
+
if (!pt) return;
|
|
2925
|
+
const displayText = unitId;
|
|
2926
|
+
let lbl = this._pointLabels.get(unitId);
|
|
2927
|
+
if (!lbl) {
|
|
2928
|
+
lbl = new QELabel(displayText, pt, labelSvg, this, labelPoints);
|
|
2929
|
+
this._pointLabels.set(unitId, lbl);
|
|
2930
|
+
} else {
|
|
2931
|
+
labelPoints === Label.DISPLAY_TYPES.ON ? lbl.show() : lbl.hide();
|
|
2932
|
+
}
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
if (labelMeans !== 0) {
|
|
2936
|
+
const meanGroupNames = hasGroupsFrame ? activeGroupRows.map((r) => r.rowName ?? String(r[0])) : Object.keys(groupSums);
|
|
2937
|
+
meanGroupNames.forEach((group) => {
|
|
2938
|
+
const meanId = `mean-${group}`;
|
|
2939
|
+
const pt = pointsLayer.__data__.objects.get(meanId);
|
|
2940
|
+
if (!pt) return;
|
|
2941
|
+
let lbl = this._meanLabels.get(group);
|
|
2942
|
+
if (!lbl) {
|
|
2943
|
+
lbl = new QELabel(group, pt, labelSvg, this, labelMeans);
|
|
2944
|
+
this._meanLabels.set(group, lbl);
|
|
2945
|
+
} else {
|
|
2946
|
+
labelMeans === Label.DISPLAY_TYPES.ON ? lbl.show() : lbl.hide();
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
pointsLayer.__data__.__state__.setVariable("layer_updated", pointsLayer.__data__);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
const networkLayer = this.__children__.search("__children__", "network");
|
|
2955
|
+
const edgeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edges");
|
|
2956
|
+
const nodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "nodes");
|
|
2957
|
+
const edgenodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edgenodes");
|
|
2958
|
+
const edgesEnabled = config.showEdges;
|
|
2959
|
+
if (((_i = model.nodes) == null ? void 0 : _i.rows) && nodeLayer) {
|
|
2960
|
+
model.nodes.rows().forEach((row) => {
|
|
2961
|
+
const pt = rowToPoint(row, nodeIdCol, "#444444");
|
|
2962
|
+
pt.size = 2;
|
|
2963
|
+
nodeLayer.__data__.objects.set(pt.ID, pt);
|
|
2964
|
+
});
|
|
2965
|
+
if (edgesEnabled && edgeLayer && ((_j = model.edges) == null ? void 0 : _j.rows)) {
|
|
2966
|
+
const unitGroupMap = {};
|
|
2967
|
+
if (groupColName && ((_k = model.points) == null ? void 0 : _k.rows)) {
|
|
2968
|
+
model.points.rows().forEach((row) => {
|
|
2969
|
+
const qIdx = row.names.indexOf(unitIdCol);
|
|
2970
|
+
const gIdx = row.names.indexOf(groupColName);
|
|
2971
|
+
if (qIdx >= 0 && gIdx >= 0) unitGroupMap[String(row[qIdx])] = String(row[gIdx]);
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
const codeNames = model.nodes.rows().map((r) => {
|
|
2975
|
+
const idx = r.names.indexOf(nodeIdCol);
|
|
2976
|
+
return idx >= 0 ? String(r[idx]) : "";
|
|
2977
|
+
}).filter(Boolean);
|
|
2978
|
+
const edgePairs = [];
|
|
2979
|
+
(model.edges.names ?? []).forEach((col) => {
|
|
2980
|
+
const pair = parseEdgePair(col, codeNames);
|
|
2981
|
+
if (pair) edgePairs.push({ col, pair });
|
|
2982
|
+
});
|
|
2983
|
+
const getUnitEdges = /* @__PURE__ */ __name((unitId) => {
|
|
2984
|
+
const result = {};
|
|
2985
|
+
model.edges.rows().forEach((row) => {
|
|
2986
|
+
const qIdx = row.names.indexOf(unitIdCol);
|
|
2987
|
+
if (qIdx < 0 || String(row[qIdx]) !== unitId) return;
|
|
2988
|
+
edgePairs.forEach(({ col }) => {
|
|
2989
|
+
const colIdx = row.names.indexOf(col);
|
|
2990
|
+
if (colIdx >= 0) result[col] = Number(row[colIdx]) || 0;
|
|
2991
|
+
});
|
|
2992
|
+
});
|
|
2993
|
+
return result;
|
|
2994
|
+
}, "getUnitEdges");
|
|
2995
|
+
const getGroupEdges = /* @__PURE__ */ __name((groupName) => {
|
|
2996
|
+
const sums = {};
|
|
2997
|
+
let count = 0;
|
|
2998
|
+
model.edges.rows().forEach((row) => {
|
|
2999
|
+
const qIdx = row.names.indexOf(unitIdCol);
|
|
3000
|
+
const unitId = qIdx >= 0 ? String(row[qIdx]) : null;
|
|
3001
|
+
const gColIdx = groupColName ? row.names.indexOf(groupColName) : -1;
|
|
3002
|
+
const group = gColIdx >= 0 ? String(row[gColIdx]) : unitId ? unitGroupMap[unitId] : null;
|
|
3003
|
+
if (group !== groupName) return;
|
|
3004
|
+
count++;
|
|
3005
|
+
edgePairs.forEach(({ col }) => {
|
|
3006
|
+
const colIdx = row.names.indexOf(col);
|
|
3007
|
+
if (colIdx >= 0) {
|
|
3008
|
+
const v = Number(row[colIdx]);
|
|
3009
|
+
if (!isNaN(v)) sums[col] = (sums[col] ?? 0) + v;
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
});
|
|
3013
|
+
if (count === 0) return {};
|
|
3014
|
+
const result = {};
|
|
3015
|
+
edgePairs.forEach(({ col }) => {
|
|
3016
|
+
result[col] = (sums[col] ?? 0) / count;
|
|
3017
|
+
});
|
|
3018
|
+
return result;
|
|
3019
|
+
}, "getGroupEdges");
|
|
3020
|
+
const resolve = /* @__PURE__ */ __name((id) => {
|
|
3021
|
+
if (groupColorMap[id]) {
|
|
3022
|
+
return { weights: getGroupEdges(id), color: groupColorMap[id], label: id };
|
|
3023
|
+
}
|
|
3024
|
+
const groupOfUnit = unitGroupMap[id] ?? null;
|
|
3025
|
+
const color = groupOfUnit ? groupColorMap[groupOfUnit] ?? "#888888" : "#888888";
|
|
3026
|
+
return { weights: getUnitEdges(id), color, label: id };
|
|
3027
|
+
}, "resolve");
|
|
3028
|
+
const unitAttr = config.edgeUnit;
|
|
3029
|
+
const groupAttr = config.edgeGroup;
|
|
3030
|
+
const compareAttr = config.edgeCompare;
|
|
3031
|
+
const alsoAttr = config.edgeAlso;
|
|
3032
|
+
const primaryId = unitAttr ?? groupAttr ?? null;
|
|
3033
|
+
const primary = primaryId ? resolve(primaryId) : null;
|
|
3034
|
+
const secondaryId = compareAttr ?? alsoAttr ?? null;
|
|
3035
|
+
const secondary = secondaryId ? resolve(secondaryId) : null;
|
|
3036
|
+
const isSubtract = !!compareAttr;
|
|
3037
|
+
const isOverlay = !!alsoAttr;
|
|
3038
|
+
const drawEdges = [];
|
|
3039
|
+
if (primary && (isSubtract || isOverlay) && secondary) {
|
|
3040
|
+
if (isSubtract) {
|
|
3041
|
+
edgePairs.forEach(({ col, pair }) => {
|
|
3042
|
+
const diff = (primary.weights[col] ?? 0) - (secondary.weights[col] ?? 0);
|
|
3043
|
+
if (Math.abs(diff) < 1e-6) return;
|
|
3044
|
+
drawEdges.push({ col, pair, weight: Math.abs(diff), color: diff > 0 ? primary.color : secondary.color });
|
|
3045
|
+
});
|
|
3046
|
+
} else {
|
|
3047
|
+
edgePairs.forEach(({ col, pair }) => {
|
|
3048
|
+
const wA = primary.weights[col] ?? 0;
|
|
3049
|
+
const wB = secondary.weights[col] ?? 0;
|
|
3050
|
+
if (wA > 0) drawEdges.push({ col: col + "-a", pair, weight: wA, color: primary.color });
|
|
3051
|
+
if (wB > 0) drawEdges.push({ col: col + "-b", pair, weight: wB, color: secondary.color });
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
} else if (primary) {
|
|
3055
|
+
edgePairs.forEach(({ col, pair }) => {
|
|
3056
|
+
const w = primary.weights[col] ?? 0;
|
|
3057
|
+
if (w > 0) drawEdges.push({ col, pair, weight: w, color: primary.color });
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
const isDirected = !!model.directed;
|
|
3061
|
+
const nonSelfWeights = drawEdges.filter((e2) => e2.pair[0] !== e2.pair[1]).map((e2) => e2.weight);
|
|
3062
|
+
const maxEdgeWeight = nonSelfWeights.length > 0 ? Math.max(...nonSelfWeights) : 1e-9;
|
|
3063
|
+
const selfWeights = drawEdges.filter((e2) => e2.pair[0] === e2.pair[1]).map((e2) => e2.weight);
|
|
3064
|
+
const maxSelfWeight = selfWeights.length > 0 ? Math.max(...selfWeights) : 0;
|
|
3065
|
+
const nodeWeightSum = {};
|
|
3066
|
+
drawEdges.forEach(({ pair, weight }) => {
|
|
3067
|
+
const isSelf = pair[0] === pair[1];
|
|
3068
|
+
if (isSelf && !isDirected) return;
|
|
3069
|
+
nodeWeightSum[pair[0]] = (nodeWeightSum[pair[0]] ?? 0) + weight;
|
|
3070
|
+
if (!isSelf) {
|
|
3071
|
+
nodeWeightSum[pair[1]] = (nodeWeightSum[pair[1]] ?? 0) + weight;
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
const maxNodeWeight = Math.max(1, ...Object.values(nodeWeightSum));
|
|
3075
|
+
const nodeSizes = {};
|
|
3076
|
+
model.nodes.rows().forEach((row) => {
|
|
3077
|
+
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3078
|
+
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
3079
|
+
if (!id) return;
|
|
3080
|
+
const pt = nodeLayer.__data__.objects.get(id);
|
|
3081
|
+
if (!pt) return;
|
|
3082
|
+
const w = nodeWeightSum[id] ?? 0;
|
|
3083
|
+
pt.size = 2 + w / maxNodeWeight * 3;
|
|
3084
|
+
nodeSizes[id] = pt.size;
|
|
3085
|
+
});
|
|
3086
|
+
const totalWeightByPair = {};
|
|
3087
|
+
if (isDirected) {
|
|
3088
|
+
drawEdges.forEach(({ pair, weight }) => {
|
|
3089
|
+
const key = `${pair[0]}.${pair[1]}`;
|
|
3090
|
+
totalWeightByPair[key] = (totalWeightByPair[key] ?? 0) + weight;
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
((_l = this.__data__.extremes) == null ? void 0 : _l.x_max) ?? 1;
|
|
3094
|
+
drawEdges.forEach(({ col, pair, weight, color }) => {
|
|
3095
|
+
const fromNode = nodeLayer.__data__.objects.get(pair[0]);
|
|
3096
|
+
const toNode = nodeLayer.__data__.objects.get(pair[1]);
|
|
3097
|
+
if (!fromNode || !toNode) return;
|
|
3098
|
+
const isSelf = pair[0] === pair[1];
|
|
3099
|
+
if (isSelf && !isDirected) return;
|
|
3100
|
+
const classMax = isSelf ? Math.max(maxSelfWeight, 1e-9) : maxEdgeWeight;
|
|
3101
|
+
const opacity = Math.min(1, weight / classMax);
|
|
3102
|
+
const fromPt = new Point({ ID: `${pair[0]}-src`, dimensions: [fromNode.x, fromNode.y], meta: {} });
|
|
3103
|
+
const toPt = new Point({ ID: `${pair[1]}-dst`, dimensions: [toNode.x, toNode.y], meta: {} });
|
|
3104
|
+
let edgeLine;
|
|
3105
|
+
if (isSelf) {
|
|
3106
|
+
edgeLine = new NodeEdge({}, fromPt, toPt);
|
|
3107
|
+
edgeLine.weight = 0;
|
|
3108
|
+
} else if (isDirected) {
|
|
3109
|
+
const fwd = totalWeightByPair[`${pair[0]}.${pair[1]}`] ?? weight;
|
|
3110
|
+
const bwd = totalWeightByPair[`${pair[1]}.${pair[0]}`] ?? 0;
|
|
3111
|
+
const totalweight = fwd + bwd;
|
|
3112
|
+
edgeLine = new TaperedWeightedMidpointEdge(
|
|
3113
|
+
{ weight, totalweight },
|
|
3114
|
+
fromPt,
|
|
3115
|
+
toPt
|
|
3116
|
+
);
|
|
3117
|
+
} else {
|
|
3118
|
+
edgeLine = new Line({}, fromPt, toPt);
|
|
3119
|
+
}
|
|
3120
|
+
edgeLine.ID = `edge-${col}`;
|
|
3121
|
+
edgeLine.color = new Color(color);
|
|
3122
|
+
edgeLine.meta = {
|
|
3123
|
+
isMeanEdge: true,
|
|
3124
|
+
weight,
|
|
3125
|
+
// NodeEdge compares within the self-loop class; cross-pair edges
|
|
3126
|
+
// compare within their own class. Store the correct denominator
|
|
3127
|
+
// so QELine.update() can compute opacity and display size properly.
|
|
3128
|
+
maxWeight: isSelf ? maxSelfWeight : maxEdgeWeight,
|
|
3129
|
+
opacity,
|
|
3130
|
+
isDirected,
|
|
3131
|
+
isSelf,
|
|
3132
|
+
// nodeSize is needed by QELine.update() to compute the NodeEdge
|
|
3133
|
+
// display radius at update time (when extremes.x_max is available).
|
|
3134
|
+
nodeSize: isSelf ? nodeSizes[pair[0]] ?? 2 : void 0
|
|
3135
|
+
};
|
|
3136
|
+
const targetLayer = isSelf ? edgenodeLayer ?? edgeLayer : edgeLayer;
|
|
3137
|
+
targetLayer.__data__.objects.set(edgeLine.ID, edgeLine);
|
|
3138
|
+
});
|
|
3139
|
+
edgeLayer.__data__.__state__.setVariable("layer_updated", edgeLayer.__data__);
|
|
3140
|
+
if (edgenodeLayer) edgenodeLayer.__data__.__state__.setVariable("layer_updated", edgenodeLayer.__data__);
|
|
3141
|
+
}
|
|
3142
|
+
if (labelSvg && labelNodes !== 0) {
|
|
3143
|
+
this._nodeLabels.forEach((lbl, id) => {
|
|
3144
|
+
if (!nodeLayer.__data__.objects.get(id)) {
|
|
3145
|
+
lbl.remove();
|
|
3146
|
+
this._nodeLabels.delete(id);
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
model.nodes.rows().forEach((row) => {
|
|
3150
|
+
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3151
|
+
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
3152
|
+
if (!id) return;
|
|
3153
|
+
const pt = nodeLayer.__data__.objects.get(id);
|
|
3154
|
+
if (!pt) return;
|
|
3155
|
+
let lbl = this._nodeLabels.get(id);
|
|
3156
|
+
if (!lbl) {
|
|
3157
|
+
lbl = new QELabel(id, pt, labelSvg, this, labelNodes);
|
|
3158
|
+
this._nodeLabels.set(id, lbl);
|
|
3159
|
+
} else {
|
|
3160
|
+
labelNodes === Label.DISPLAY_TYPES.ON ? lbl.show() : lbl.hide();
|
|
3161
|
+
}
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
nodeLayer.__data__.__state__.setVariable("layer_updated", nodeLayer.__data__);
|
|
3165
|
+
}
|
|
3166
|
+
const axisPointsLayer = this.__children__.search("__children__", "axis_points");
|
|
3167
|
+
const axisLinesLayer = this.__children__.search("__children__", "axis_lines");
|
|
3168
|
+
if (axisPointsLayer) this.__data__.__state__.setVariable("graph_updated_axis_points", axisPointsLayer.__data__.objects);
|
|
3169
|
+
if (axisLinesLayer) this.__data__.__state__.setVariable("graph_updated_axis_lines", axisLinesLayer.__data__.objects);
|
|
3170
|
+
this.update();
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
static get observedAttributes() {
|
|
3174
|
+
return [
|
|
3175
|
+
"width",
|
|
3176
|
+
"height",
|
|
3177
|
+
"for",
|
|
3178
|
+
"scale-points",
|
|
3179
|
+
"labels",
|
|
3180
|
+
"label-nodes",
|
|
3181
|
+
"label-means",
|
|
3182
|
+
"label-points"
|
|
3183
|
+
];
|
|
3184
|
+
}
|
|
3185
|
+
get vizena() {
|
|
3186
|
+
return this._vizena;
|
|
3187
|
+
}
|
|
3188
|
+
// ── Shared re-render scheduler ───────────────────────────────────────────────
|
|
3189
|
+
// Batches synchronous attribute / child-list changes into a single microtask
|
|
3190
|
+
// so that e.g. adding <qe-edges> and setting its attributes in the same tick
|
|
3191
|
+
// only triggers one render cycle.
|
|
3192
|
+
_scheduleRerender() {
|
|
3193
|
+
if (!this._vizena || !this._hasModel) return;
|
|
3194
|
+
if (this._renderPending) return;
|
|
3195
|
+
this._renderPending = true;
|
|
3196
|
+
queueMicrotask(() => {
|
|
3197
|
+
var _a;
|
|
3198
|
+
this._renderPending = false;
|
|
3199
|
+
(_a = this._vizena) == null ? void 0 : _a.dispatchEvent(new CustomEvent("model-updated", {
|
|
3200
|
+
detail: this._vizena.model,
|
|
3201
|
+
bubbles: true,
|
|
3202
|
+
composed: true
|
|
3203
|
+
}));
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
// ── Layer configuration reader ────────────────────────────────────────────────
|
|
3207
|
+
// Reads child element presence / attributes to determine what each layer
|
|
3208
|
+
// should render. Called once at the top of every model-updated handler so
|
|
3209
|
+
// all rendering decisions are derived from a single snapshot.
|
|
3210
|
+
_readLayerConfig() {
|
|
3211
|
+
const nodesEl = this.querySelector("qe-nodes");
|
|
3212
|
+
const meansEl = this.querySelector("qe-means");
|
|
3213
|
+
const pointsEl = this.querySelector("qe-points");
|
|
3214
|
+
const edgesEl = this.querySelector("qe-edges");
|
|
3215
|
+
const labelsShorthand = this.getAttribute("labels");
|
|
3216
|
+
return {
|
|
3217
|
+
// Nodes — always rendered (coordinate anchors); <qe-nodes> controls labels.
|
|
3218
|
+
labelNodes: parseLabelMode(
|
|
3219
|
+
(nodesEl == null ? void 0 : nodesEl.getAttribute("label")) ?? this.getAttribute("label-nodes") ?? labelsShorthand,
|
|
3220
|
+
Label.DISPLAY_TYPES.ON
|
|
3221
|
+
),
|
|
3222
|
+
scalePoints: this.getAttribute("scale-points") !== "false",
|
|
3223
|
+
// Means — presence of <qe-means> = show; absence = hide.
|
|
3224
|
+
showMeans: !!meansEl,
|
|
3225
|
+
activeMeanGroups: (meansEl == null ? void 0 : meansEl.getAttribute("groups")) ?? null,
|
|
3226
|
+
showCI: !!meansEl && meansEl.hasAttribute("confidence"),
|
|
3227
|
+
labelMeans: parseLabelMode(
|
|
3228
|
+
(meansEl == null ? void 0 : meansEl.getAttribute("label")) ?? this.getAttribute("label-means") ?? labelsShorthand,
|
|
3229
|
+
Label.DISPLAY_TYPES.ON
|
|
3230
|
+
),
|
|
3231
|
+
// Points — presence of <qe-points> = show unit dots.
|
|
3232
|
+
showPoints: !!pointsEl,
|
|
3233
|
+
labelPoints: parseLabelMode(
|
|
3234
|
+
(pointsEl == null ? void 0 : pointsEl.getAttribute("label")) ?? this.getAttribute("label-points") ?? labelsShorthand,
|
|
3235
|
+
Label.DISPLAY_TYPES.OFF
|
|
3236
|
+
),
|
|
3237
|
+
// Edges — presence of <qe-edges> = draw a network.
|
|
3238
|
+
showEdges: !!edgesEl,
|
|
3239
|
+
edgeGroup: (edgesEl == null ? void 0 : edgesEl.getAttribute("group")) ?? null,
|
|
3240
|
+
edgeUnit: (edgesEl == null ? void 0 : edgesEl.getAttribute("unit")) ?? null,
|
|
3241
|
+
edgeCompare: (edgesEl == null ? void 0 : edgesEl.getAttribute("compare")) ?? null,
|
|
3242
|
+
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
attributeChangedCallback(_name2, oldValue, newValue) {
|
|
3246
|
+
if (oldValue === newValue) return;
|
|
3247
|
+
this._scheduleRerender();
|
|
3248
|
+
}
|
|
3249
|
+
connectedCallback() {
|
|
3250
|
+
var _a;
|
|
3251
|
+
const w = this.getAttribute("width");
|
|
3252
|
+
const h = this.getAttribute("height");
|
|
3253
|
+
if (w) this.style.width = isNaN(Number(w)) ? w : `${w}px`;
|
|
3254
|
+
if (h) this.style.height = isNaN(Number(h)) ? h : `${h}px`;
|
|
3255
|
+
this.appendChild(this.svg_element);
|
|
3256
|
+
this._childObserver = new MutationObserver(() => this._scheduleRerender());
|
|
3257
|
+
this._childObserver.observe(this, {
|
|
3258
|
+
childList: true,
|
|
3259
|
+
attributes: true,
|
|
3260
|
+
subtree: true,
|
|
3261
|
+
attributeFilter: [
|
|
3262
|
+
"group",
|
|
3263
|
+
"unit",
|
|
3264
|
+
"compare",
|
|
3265
|
+
"also",
|
|
3266
|
+
"groups",
|
|
3267
|
+
"confidence",
|
|
3268
|
+
"label",
|
|
3269
|
+
"scale-points",
|
|
3270
|
+
"labels",
|
|
3271
|
+
"label-nodes",
|
|
3272
|
+
"label-means",
|
|
3273
|
+
"label-points"
|
|
3274
|
+
]
|
|
3275
|
+
});
|
|
3276
|
+
const forId = this.getAttribute("for");
|
|
3277
|
+
this._vizena = (forId ? document.getElementById(forId) : null) ?? this.closest("qe-visual") ?? ((_a = this.parentElement) == null ? void 0 : _a.closest("qe-visual")) ?? document.querySelector("qe-visual");
|
|
3278
|
+
this.draw();
|
|
3279
|
+
const labelsLayer = this.__children__.search("__children__", "labels");
|
|
3280
|
+
if (labelsLayer == null ? void 0 : labelsLayer.svg_element) {
|
|
3281
|
+
this._labelsGroup = labelsLayer.svg_element;
|
|
3282
|
+
} else {
|
|
3283
|
+
console.warn("[ena-graph] labels layer not found in layer tree; creating fallback <g>");
|
|
3284
|
+
const graphLayer = this.__children__[0];
|
|
3285
|
+
if (graphLayer == null ? void 0 : graphLayer.svg_element) {
|
|
3286
|
+
this._labelsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
3287
|
+
this._labelsGroup.setAttribute("data-layer-name", "labels-fallback");
|
|
3288
|
+
graphLayer.svg_element.appendChild(this._labelsGroup);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
disconnectedCallback() {
|
|
3293
|
+
var _a;
|
|
3294
|
+
(_a = this._childObserver) == null ? void 0 : _a.disconnect();
|
|
3295
|
+
this._childObserver = null;
|
|
3296
|
+
}
|
|
3297
|
+
create() {
|
|
3298
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3299
|
+
svg.classList.add("graph");
|
|
3300
|
+
svg.setAttribute("viewBox", this.__data__.extremes.as_viewBox());
|
|
3301
|
+
svg.setAttribute("transform", this.__data__.transform.toString());
|
|
3302
|
+
svg.setAttribute("role", "figure");
|
|
3303
|
+
svg.setAttribute("data-graph-size", `${Math.min(this.__data__.extremes.width, this.__data__.extremes.height)}`);
|
|
3304
|
+
this.svg_element = svg;
|
|
3305
|
+
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
3306
|
+
const AW = 0.22, AH = 0.15;
|
|
3307
|
+
const makeArrow = /* @__PURE__ */ __name((id, fill) => {
|
|
3308
|
+
const m = document.createElementNS("http://www.w3.org/2000/svg", "marker");
|
|
3309
|
+
m.setAttribute("id", id);
|
|
3310
|
+
m.setAttribute("markerWidth", String(AW));
|
|
3311
|
+
m.setAttribute("markerHeight", String(AH));
|
|
3312
|
+
m.setAttribute("refX", String(AW));
|
|
3313
|
+
m.setAttribute("refY", String(AH / 2));
|
|
3314
|
+
m.setAttribute("orient", "auto");
|
|
3315
|
+
m.setAttribute("markerUnits", "userSpaceOnUse");
|
|
3316
|
+
const p = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
3317
|
+
p.setAttribute("d", `M0,0 L0,${AH} L${AW},${AH / 2} z`);
|
|
3318
|
+
p.setAttribute("fill", fill);
|
|
3319
|
+
m.appendChild(p);
|
|
3320
|
+
defs.appendChild(m);
|
|
3321
|
+
}, "makeArrow");
|
|
3322
|
+
GROUP_PALETTE.forEach((color) => makeArrow(`qe-arrow-${color.slice(1)}`, color));
|
|
3323
|
+
svg.appendChild(defs);
|
|
3324
|
+
this.__data__.layers.forEach((layer) => {
|
|
3325
|
+
const layer_g = new QELayer(layer, this.svg_element, this);
|
|
3326
|
+
this.__children__.push(layer_g);
|
|
3327
|
+
});
|
|
3328
|
+
this.__data__.__state__.setVariable("graph_updated", this.__data__);
|
|
3329
|
+
this.attach_events();
|
|
3330
|
+
return svg;
|
|
3331
|
+
}
|
|
3332
|
+
attach_events() {
|
|
3333
|
+
this.svg_element.addEventListener("wheel", (ev) => {
|
|
3334
|
+
console.log("Wheel event: ", ev);
|
|
3335
|
+
ev.preventDefault();
|
|
3336
|
+
const delta = Math.sign(ev.deltaY);
|
|
3337
|
+
if (delta < 0) {
|
|
3338
|
+
this.zoom_level *= 1.1;
|
|
3339
|
+
} else {
|
|
3340
|
+
this.zoom_level /= 1.1;
|
|
3341
|
+
}
|
|
3342
|
+
this.__data__.transform.zoom(this.zoom_level);
|
|
3343
|
+
this.update();
|
|
3344
|
+
});
|
|
3345
|
+
let isPanning = false;
|
|
3346
|
+
let startX, startY;
|
|
3347
|
+
this.svg_element.addEventListener("mousedown", (ev) => {
|
|
3348
|
+
isPanning = true;
|
|
3349
|
+
startX = ev.clientX;
|
|
3350
|
+
startY = ev.clientY;
|
|
3351
|
+
});
|
|
3352
|
+
let panAnimationFrame = null;
|
|
3353
|
+
let pendingPan = null;
|
|
3354
|
+
const panUpdate = /* @__PURE__ */ __name(() => {
|
|
3355
|
+
if (pendingPan) {
|
|
3356
|
+
this.__data__.transform.pan(pendingPan.dx, pendingPan.dy);
|
|
3357
|
+
this.__children__[0].svg_element.setAttribute("transform", this.__data__.transform.toString());
|
|
3358
|
+
pendingPan = null;
|
|
3359
|
+
}
|
|
3360
|
+
panAnimationFrame = null;
|
|
3361
|
+
}, "panUpdate");
|
|
3362
|
+
this.svg_element.addEventListener("mousemove", (ev) => {
|
|
3363
|
+
if (!isPanning) return;
|
|
3364
|
+
const dx = ev.clientX - startX, dy = ev.clientY - startY;
|
|
3365
|
+
pendingPan = { dx: dx / 100, dy: dy / 100 };
|
|
3366
|
+
startX = ev.clientX;
|
|
3367
|
+
startY = ev.clientY;
|
|
3368
|
+
if (panAnimationFrame === null) {
|
|
3369
|
+
panAnimationFrame = requestAnimationFrame(panUpdate);
|
|
3370
|
+
}
|
|
3371
|
+
});
|
|
3372
|
+
this.svg_element.addEventListener("mouseup", (ev) => {
|
|
3373
|
+
isPanning = false;
|
|
3374
|
+
});
|
|
3375
|
+
this.svg_element.addEventListener("point-clicked", ((ev) => {
|
|
3376
|
+
var _a, _b;
|
|
3377
|
+
const id = (_b = (_a = ev.detail) == null ? void 0 : _a.pointData) == null ? void 0 : _b.ID;
|
|
3378
|
+
if (!id) return;
|
|
3379
|
+
const lbl = this._pointLabels.get(id) ?? this._meanLabels.get(id.replace("mean-", "")) ?? this._nodeLabels.get(id);
|
|
3380
|
+
if (lbl && lbl.displayMode === Label.DISPLAY_TYPES.CLICK) lbl.toggle();
|
|
3381
|
+
}));
|
|
3382
|
+
const findPointId = /* @__PURE__ */ __name((el) => {
|
|
3383
|
+
var _a;
|
|
3384
|
+
while (el && el !== this.svg_element) {
|
|
3385
|
+
const id = (_a = el.getAttribute) == null ? void 0 : _a.call(el, "data-point-id");
|
|
3386
|
+
if (id) return id;
|
|
3387
|
+
el = el.parentElement;
|
|
3388
|
+
}
|
|
3389
|
+
return null;
|
|
3390
|
+
}, "findPointId");
|
|
3391
|
+
const getAutoLabel = /* @__PURE__ */ __name((id) => {
|
|
3392
|
+
return this._pointLabels.get(id) ?? this._meanLabels.get(id.replace(/^mean-/, "")) ?? this._nodeLabels.get(id);
|
|
3393
|
+
}, "getAutoLabel");
|
|
3394
|
+
this.svg_element.addEventListener("mouseover", (ev) => {
|
|
3395
|
+
const id = findPointId(ev.target);
|
|
3396
|
+
if (!id) return;
|
|
3397
|
+
const lbl = getAutoLabel(id);
|
|
3398
|
+
if ((lbl == null ? void 0 : lbl.displayMode) === Label.DISPLAY_TYPES.AUTO) lbl.show();
|
|
3399
|
+
});
|
|
3400
|
+
this.svg_element.addEventListener("mouseout", (ev) => {
|
|
3401
|
+
const id = findPointId(ev.target);
|
|
3402
|
+
if (!id) return;
|
|
3403
|
+
const related = ev.relatedTarget;
|
|
3404
|
+
const relatedId = findPointId(related);
|
|
3405
|
+
if (relatedId === id) return;
|
|
3406
|
+
const lbl = getAutoLabel(id);
|
|
3407
|
+
if ((lbl == null ? void 0 : lbl.displayMode) === Label.DISPLAY_TYPES.AUTO) lbl.hide();
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
draw() {
|
|
3411
|
+
var _a;
|
|
3412
|
+
if (!this.svg_element) {
|
|
3413
|
+
console.error("No element to draw to: ", this);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
(_a = this.wrapper) == null ? void 0 : _a.appendChild(this.svg_element);
|
|
3417
|
+
this.__children__.forEach((enaLayer) => {
|
|
3418
|
+
enaLayer.draw();
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3421
|
+
update() {
|
|
3422
|
+
this.update_extremes();
|
|
3423
|
+
this.update_layers();
|
|
3424
|
+
this.update_labels();
|
|
3425
|
+
this.update_extremes();
|
|
3426
|
+
}
|
|
3427
|
+
update_labels() {
|
|
3428
|
+
this._nodeLabels.forEach((lbl) => lbl.update());
|
|
3429
|
+
this._meanLabels.forEach((lbl) => lbl.update());
|
|
3430
|
+
this._pointLabels.forEach((lbl) => lbl.update());
|
|
3431
|
+
}
|
|
3432
|
+
update_extremes() {
|
|
3433
|
+
var _a;
|
|
3434
|
+
this.__data__.update_extremes();
|
|
3435
|
+
const svgRect = (_a = this.svg_element) == null ? void 0 : _a.getBoundingClientRect();
|
|
3436
|
+
if (this.svg_element && svgRect) {
|
|
3437
|
+
console.log("SVG rect: ", svgRect);
|
|
3438
|
+
let w, h;
|
|
3439
|
+
if (svgRect.width === 0 || svgRect.height === 0) {
|
|
3440
|
+
w = 1;
|
|
3441
|
+
h = 1;
|
|
3442
|
+
} else {
|
|
3443
|
+
w = svgRect.width;
|
|
3444
|
+
h = svgRect.height;
|
|
3445
|
+
}
|
|
3446
|
+
this.__data__.scale_x = 1 / (w / (4 * this.__data__.extremes.x_max));
|
|
3447
|
+
this.__data__.scale_y = 1 / (h / (4 * this.__data__.extremes.y_max));
|
|
3448
|
+
this.__children__[0].svg_element.setAttribute("transform", this.__data__.transform.toString());
|
|
3449
|
+
this.svg_element.setAttribute("viewBox", this.__data__.extremes.as_viewBox());
|
|
3450
|
+
let axisLineLayer = this.__data__.layers.search("layers", "axis_lines");
|
|
3451
|
+
if (axisLineLayer) {
|
|
3452
|
+
let axis_x = axisLineLayer.objects.get("axis-x"), axis_y = axisLineLayer.objects.get("axis-y");
|
|
3453
|
+
if (axis_x && axis_y) {
|
|
3454
|
+
axis_x.from = new Point({ ID: "axis-x-start", dimensions: [-this.__data__.extremes.x_max, 0], classes: ["ena.dimension", "ena.dimension"] });
|
|
3455
|
+
axis_x.to = new Point({ ID: "axis-x-end", dimensions: [this.__data__.extremes.x_max, 0], classes: ["ena.dimension", "ena.dimension"] });
|
|
3456
|
+
axis_y.from = new Point({ ID: "axis-y-start", dimensions: [0, -this.__data__.extremes.y_max], classes: ["ena.dimension", "ena.dimension"] });
|
|
3457
|
+
axis_y.to = new Point({ ID: "axis-y-end", dimensions: [0, this.__data__.extremes.y_max], classes: ["ena.dimension", "ena.dimension"] });
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
update_layers() {
|
|
3463
|
+
this.__children__.forEach((enaLayer) => {
|
|
3464
|
+
enaLayer.update();
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
__name(_QEGraphElement, "QEGraphElement");
|
|
3469
|
+
_QEGraphElement.tagName = "qe-graph";
|
|
3470
|
+
_QEGraphElement.style = `
|
|
3471
|
+
${DrawableElement.style}
|
|
3472
|
+
|
|
3473
|
+
:host {
|
|
3474
|
+
display: block;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
.wrapper {
|
|
3478
|
+
width: 100%;
|
|
3479
|
+
height: 100%;
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
svg {
|
|
3483
|
+
width: 100%;
|
|
3484
|
+
height: 100%;
|
|
3485
|
+
aspect-ratio: 1 / 1;
|
|
3486
|
+
background-color: #FFF;
|
|
3487
|
+
}
|
|
3488
|
+
`;
|
|
3489
|
+
let QEGraphElement = _QEGraphElement;
|
|
3490
|
+
customElements.define(QEGraphElement.tagName, QEGraphElement);
|
|
3491
|
+
exports2.Color = Color;
|
|
3492
|
+
exports2.DataFrame = DataFrame;
|
|
3493
|
+
exports2.DataFrameColumn = DataFrameColumn;
|
|
3494
|
+
exports2.DataFrameColumnType = DataFrameColumnType;
|
|
3495
|
+
exports2.DataFrameRow = DataFrameRow;
|
|
3496
|
+
exports2.Edge = Edge;
|
|
3497
|
+
exports2.Graph = Graph;
|
|
3498
|
+
exports2.QEEdgesElement = QEEdgesElement;
|
|
3499
|
+
exports2.QEGraphElement = QEGraphElement;
|
|
3500
|
+
exports2.QELayer = QELayer;
|
|
3501
|
+
exports2.QELine = QELine;
|
|
3502
|
+
exports2.QEMeansElement = QEMeansElement;
|
|
3503
|
+
exports2.QENodesElement = QENodesElement;
|
|
3504
|
+
exports2.QEPoint = QEPoint;
|
|
3505
|
+
exports2.QEPointsElement = QEPointsElement;
|
|
3506
|
+
exports2.QEVisualElement = QEVisualElement;
|
|
3507
|
+
exports2.WeightedColor = WeightedColor;
|
|
3508
|
+
exports2.vizENA = vizENA;
|
|
3509
|
+
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
3510
|
+
}));
|
|
3511
|
+
//# sourceMappingURL=qeviz.umd.js.map
|