@qe-libs/qeviz 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -3
- package/dist/qeviz.es.js +767 -63
- package/dist/qeviz.es.js.map +1 -1
- package/dist/qeviz.umd.js +767 -63
- package/dist/qeviz.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/qeviz.umd.js
CHANGED
|
@@ -599,9 +599,10 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
599
599
|
__name(_Color, "Color");
|
|
600
600
|
let Color = _Color;
|
|
601
601
|
const _WeightedColor = class _WeightedColor extends Color {
|
|
602
|
-
constructor(base, weight) {
|
|
602
|
+
constructor(base, weight, alpha) {
|
|
603
603
|
super(base);
|
|
604
604
|
this.weight = weight;
|
|
605
|
+
this.alpha = alpha;
|
|
605
606
|
}
|
|
606
607
|
// Parse hex → HSV (h in [0, 360), s/v in [0, 1]).
|
|
607
608
|
static toHSV(hex) {
|
|
@@ -672,7 +673,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
672
673
|
toString() {
|
|
673
674
|
const { h, v } = _WeightedColor.toHSV(this.base);
|
|
674
675
|
const w = Math.min(1, Math.abs(this.weight));
|
|
675
|
-
|
|
676
|
+
const a = this.alpha === void 0 ? w : Math.min(1, Math.max(0, this.alpha));
|
|
677
|
+
return _WeightedColor.hsvToRgba(h, w, v, a);
|
|
676
678
|
}
|
|
677
679
|
};
|
|
678
680
|
__name(_WeightedColor, "WeightedColor");
|
|
@@ -2184,9 +2186,22 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2184
2186
|
return __privateGet(this, _edge);
|
|
2185
2187
|
}
|
|
2186
2188
|
get path() {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2189
|
+
const _this = __privateGet(this, _edge);
|
|
2190
|
+
const T = [_this.to.x, _this.to.y];
|
|
2191
|
+
const A = _this.from_point;
|
|
2192
|
+
const [B0, B1] = _this.base_pts;
|
|
2193
|
+
const L = Math.hypot(A[0] - T[0], A[1] - T[1]);
|
|
2194
|
+
const W = Math.hypot(B0[0] - B1[0], B0[1] - B1[1]);
|
|
2195
|
+
if (!(L > 0) || !(W > 0)) return "";
|
|
2196
|
+
const u = [(A[0] - T[0]) / L, (A[1] - T[1]) / L];
|
|
2197
|
+
const b = Math.hypot(B0[0] - T[0], B0[1] - T[1]);
|
|
2198
|
+
const v = [(B0[0] - T[0]) / b, (B0[1] - T[1]) / b];
|
|
2199
|
+
const halfW = /* @__PURE__ */ __name((d) => W / 2 * Math.max(0, 1 - d / L), "halfW");
|
|
2200
|
+
const at = /* @__PURE__ */ __name((d, s) => `${T[0] + u[0] * d + v[0] * s} ${T[1] + u[1] * d + v[1] * s}`, "at");
|
|
2201
|
+
const dh = 0.2 * L;
|
|
2202
|
+
const c = Math.min(0.15 * L, 0.8 * 2 * halfW(dh));
|
|
2203
|
+
const dm = dh + c / 2, dn = dh + c;
|
|
2204
|
+
const p = `M${at(dh, 0)} L${at(dm, halfW(dm))} L${at(dn, halfW(dn))} L${at(dm, 0)} L${at(dn, -halfW(dn))} L${at(dm, -halfW(dm))}z`;
|
|
2190
2205
|
return p;
|
|
2191
2206
|
}
|
|
2192
2207
|
};
|
|
@@ -2329,6 +2344,46 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2329
2344
|
};
|
|
2330
2345
|
__name(_Drawable, "Drawable");
|
|
2331
2346
|
let Drawable = _Drawable;
|
|
2347
|
+
const DEFAULT_EDGE_WIDTH = 0.04;
|
|
2348
|
+
const SATURATION_RANGE = [0.25, 1];
|
|
2349
|
+
const OPACITY_RANGE = [0.3, 1];
|
|
2350
|
+
function rampFor(w, lo, hi, range) {
|
|
2351
|
+
const a = Math.abs(w);
|
|
2352
|
+
if (!(a > 0)) return 0;
|
|
2353
|
+
if (!(hi > lo)) return range[1];
|
|
2354
|
+
const t = Math.min(1, Math.max(0, (a - lo) / (hi - lo)));
|
|
2355
|
+
return range[0] + t * (range[1] - range[0]);
|
|
2356
|
+
}
|
|
2357
|
+
__name(rampFor, "rampFor");
|
|
2358
|
+
const EDGE_WIDTH_PER_EXTENT = 0.011;
|
|
2359
|
+
const TAPERED_OUTLINE_PX = 0.75;
|
|
2360
|
+
const CHEVRON_DARKEN = 0.8;
|
|
2361
|
+
const CHEVRON_HALO_PX = 0.75;
|
|
2362
|
+
const POINT_RADIUS_PER_EXTENT = 6e-3;
|
|
2363
|
+
const UNCONNECTED_NODE_SIZE = 0.6;
|
|
2364
|
+
function widthPerWeight(edgeWidth) {
|
|
2365
|
+
return 2 * edgeWidth;
|
|
2366
|
+
}
|
|
2367
|
+
__name(widthPerWeight, "widthPerWeight");
|
|
2368
|
+
function nodeSizeForEdges(weightSum, widthFrac) {
|
|
2369
|
+
return weightSum * widthFrac / (2 * POINT_RADIUS_PER_EXTENT);
|
|
2370
|
+
}
|
|
2371
|
+
__name(nodeSizeForEdges, "nodeSizeForEdges");
|
|
2372
|
+
function flattenOnWhite(col) {
|
|
2373
|
+
const m = /rgba?\(([^)]+)\)/.exec(col);
|
|
2374
|
+
if (!m) return col;
|
|
2375
|
+
const [r, g, b, a = 1] = m[1].split(",").map(Number);
|
|
2376
|
+
const f = /* @__PURE__ */ __name((c) => Math.round(255 + (c - 255) * a), "f");
|
|
2377
|
+
return `rgb(${f(r)},${f(g)},${f(b)})`;
|
|
2378
|
+
}
|
|
2379
|
+
__name(flattenOnWhite, "flattenOnWhite");
|
|
2380
|
+
function darkenRgba(col, k) {
|
|
2381
|
+
const m = /rgba?\(([^)]+)\)/.exec(col);
|
|
2382
|
+
if (!m) return col;
|
|
2383
|
+
const [r, g, b, a = 1] = m[1].split(",").map(Number);
|
|
2384
|
+
return `rgba(${Math.round(r * k)},${Math.round(g * k)},${Math.round(b * k)},${a})`;
|
|
2385
|
+
}
|
|
2386
|
+
__name(darkenRgba, "darkenRgba");
|
|
2332
2387
|
const _QELine = class _QELine extends Drawable {
|
|
2333
2388
|
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2334
2389
|
super(__data__, parent, graph, __children__);
|
|
@@ -2338,8 +2393,22 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2338
2393
|
this.__children__ = __children__;
|
|
2339
2394
|
this.__name__ = "unnamed-line";
|
|
2340
2395
|
this._arrow_el = null;
|
|
2396
|
+
this._hit_el = null;
|
|
2341
2397
|
this.svg_element = this.create();
|
|
2342
2398
|
}
|
|
2399
|
+
/** Stamp edge identity onto an element so <qe-graph> delegation can resolve
|
|
2400
|
+
* qe-edge-* events. No-op for non-edges (e.g. axis lines). */
|
|
2401
|
+
_stampEdge(el) {
|
|
2402
|
+
const meta = this.__data__.meta ?? {};
|
|
2403
|
+
if (!meta.isMeanEdge) return;
|
|
2404
|
+
el.setAttribute("data-edge-source", String(meta.source ?? ""));
|
|
2405
|
+
el.setAttribute("data-edge-target", String(meta.target ?? ""));
|
|
2406
|
+
el.setAttribute("data-edge-weight", String(meta.weight ?? 0));
|
|
2407
|
+
el.setAttribute("data-edge-directed", meta.isDirected ? "true" : "false");
|
|
2408
|
+
if (meta.isSelf) el.setAttribute("data-edge-self", "true");
|
|
2409
|
+
if (meta.group != null) el.setAttribute("data-edge-group", String(meta.group));
|
|
2410
|
+
if (meta.unit != null) el.setAttribute("data-edge-unit", String(meta.unit));
|
|
2411
|
+
}
|
|
2343
2412
|
create() {
|
|
2344
2413
|
if (this.__data__ instanceof NodeEdge) {
|
|
2345
2414
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
@@ -2376,24 +2445,28 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2376
2445
|
const extent = ((_a = this.graph.__data__.extremes) == null ? void 0 : _a.x_max) ?? 1;
|
|
2377
2446
|
const meta = this.__data__.meta ?? {};
|
|
2378
2447
|
if (meta.isMeanEdge) {
|
|
2448
|
+
this._stampEdge(this.svg_element);
|
|
2379
2449
|
const weight = typeof meta.weight === "number" ? meta.weight : 0;
|
|
2380
2450
|
const maxW = typeof meta.maxWeight === "number" ? meta.maxWeight : 1;
|
|
2381
2451
|
const opacity = typeof meta.opacity === "number" ? meta.opacity : Math.min(1, weight / (maxW || 1));
|
|
2382
|
-
const
|
|
2452
|
+
const ext = extent > 0 ? extent : 1;
|
|
2453
|
+
const sw = typeof meta.widthFrac === "number" ? ext * meta.widthFrac * weight : ext * EDGE_WIDTH_PER_EXTENT * (weight / (maxW || 1));
|
|
2383
2454
|
const baseColor = this.__data__._color || new Color("#000000");
|
|
2384
|
-
const
|
|
2455
|
+
const saturation = typeof meta.saturation === "number" ? meta.saturation : opacity;
|
|
2456
|
+
const edgeColor = new WeightedColor(baseColor, saturation, opacity).toString();
|
|
2385
2457
|
if (this.__data__ instanceof NodeEdge) {
|
|
2386
|
-
|
|
2387
|
-
const nodeR = extent * 6e-3 * nodeSize;
|
|
2388
|
-
this.__data__.weight = 2 * nodeR * 0.55 * opacity;
|
|
2458
|
+
this.__data__.weight = sw;
|
|
2389
2459
|
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2390
2460
|
this.svg_element.setAttribute("fill", edgeColor);
|
|
2391
2461
|
this.svg_element.setAttribute("stroke", "none");
|
|
2392
2462
|
} else if (this.__data__ instanceof TaperedEdge) {
|
|
2393
|
-
this.__data__.weight = sw
|
|
2463
|
+
this.__data__.weight = sw;
|
|
2394
2464
|
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2395
2465
|
this.svg_element.setAttribute("fill", edgeColor);
|
|
2396
|
-
this.svg_element.setAttribute("stroke",
|
|
2466
|
+
this.svg_element.setAttribute("stroke", edgeColor);
|
|
2467
|
+
this.svg_element.setAttribute("stroke-width", String(TAPERED_OUTLINE_PX));
|
|
2468
|
+
this.svg_element.setAttribute("vector-effect", "non-scaling-stroke");
|
|
2469
|
+
this.svg_element.setAttribute("stroke-linejoin", "round");
|
|
2397
2470
|
if (meta.isDirected) {
|
|
2398
2471
|
try {
|
|
2399
2472
|
const arrow = new EdgeArrow(this.__data__);
|
|
@@ -2407,20 +2480,39 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2407
2480
|
this.parent.appendChild(this._arrow_el);
|
|
2408
2481
|
}
|
|
2409
2482
|
this._arrow_el.setAttribute("d", arrowPath);
|
|
2410
|
-
|
|
2411
|
-
this._arrow_el.setAttribute("
|
|
2483
|
+
this._arrow_el.setAttribute("fill", darkenRgba(flattenOnWhite(edgeColor), CHEVRON_DARKEN));
|
|
2484
|
+
this._arrow_el.setAttribute("stroke", "#ffffff");
|
|
2485
|
+
this._arrow_el.setAttribute("stroke-width", String(CHEVRON_HALO_PX));
|
|
2486
|
+
this._arrow_el.setAttribute("vector-effect", "non-scaling-stroke");
|
|
2487
|
+
this._arrow_el.setAttribute("stroke-linejoin", "round");
|
|
2412
2488
|
}
|
|
2413
2489
|
} catch (_) {
|
|
2414
2490
|
}
|
|
2415
2491
|
}
|
|
2416
2492
|
} else {
|
|
2417
|
-
this.
|
|
2418
|
-
this.
|
|
2419
|
-
this.svg_element.setAttribute("
|
|
2420
|
-
this.svg_element.setAttribute("
|
|
2493
|
+
const x1 = this.__data__.from.x, y1 = this.__data__.from.y;
|
|
2494
|
+
const x2 = this.__data__.to.x, y2 = this.__data__.to.y;
|
|
2495
|
+
this.svg_element.setAttribute("x1", x1.toString());
|
|
2496
|
+
this.svg_element.setAttribute("y1", y1.toString());
|
|
2497
|
+
this.svg_element.setAttribute("x2", x2.toString());
|
|
2498
|
+
this.svg_element.setAttribute("y2", y2.toString());
|
|
2421
2499
|
this.svg_element.setAttribute("stroke-width", sw.toString());
|
|
2422
2500
|
this.svg_element.setAttribute("stroke", edgeColor);
|
|
2501
|
+
this.svg_element.setAttribute("pointer-events", "stroke");
|
|
2423
2502
|
this.svg_element.removeAttribute("marker-end");
|
|
2503
|
+
if (!this._hit_el) {
|
|
2504
|
+
this._hit_el = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
2505
|
+
this._hit_el.setAttribute("stroke", "transparent");
|
|
2506
|
+
this._hit_el.setAttribute("stroke-linecap", "round");
|
|
2507
|
+
this._hit_el.setAttribute("pointer-events", "stroke");
|
|
2508
|
+
}
|
|
2509
|
+
if (!this._hit_el.parentElement) this.parent.appendChild(this._hit_el);
|
|
2510
|
+
this._hit_el.setAttribute("x1", x1.toString());
|
|
2511
|
+
this._hit_el.setAttribute("y1", y1.toString());
|
|
2512
|
+
this._hit_el.setAttribute("x2", x2.toString());
|
|
2513
|
+
this._hit_el.setAttribute("y2", y2.toString());
|
|
2514
|
+
this._hit_el.setAttribute("stroke-width", Math.max(sw, extent * 0.03).toString());
|
|
2515
|
+
this._stampEdge(this._hit_el);
|
|
2424
2516
|
}
|
|
2425
2517
|
} else {
|
|
2426
2518
|
this.svg_element.setAttribute("x1", this.__data__.from.x.toString());
|
|
@@ -2434,10 +2526,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2434
2526
|
}
|
|
2435
2527
|
}
|
|
2436
2528
|
draw() {
|
|
2437
|
-
var _a, _b;
|
|
2438
|
-
(_a = this.parent) == null ? void 0 : _a.appendChild(this.
|
|
2529
|
+
var _a, _b, _c;
|
|
2530
|
+
if (this._hit_el) (_a = this.parent) == null ? void 0 : _a.appendChild(this._hit_el);
|
|
2531
|
+
(_b = this.parent) == null ? void 0 : _b.appendChild(this.svg_element);
|
|
2439
2532
|
if (this._arrow_el) {
|
|
2440
|
-
(
|
|
2533
|
+
(_c = this.parent) == null ? void 0 : _c.appendChild(this._arrow_el);
|
|
2441
2534
|
}
|
|
2442
2535
|
}
|
|
2443
2536
|
};
|
|
@@ -2467,8 +2560,23 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2467
2560
|
* (≈ 1–2 % of the coordinate half-extent for a typical ENA network).
|
|
2468
2561
|
*/
|
|
2469
2562
|
computeRadius() {
|
|
2470
|
-
|
|
2471
|
-
|
|
2563
|
+
var _a;
|
|
2564
|
+
const r = this.baseRadius();
|
|
2565
|
+
const cap = (_a = this.__data__.meta) == null ? void 0 : _a.maxRadius;
|
|
2566
|
+
return typeof cap === "number" && cap > 0 ? Math.min(r, cap) : r;
|
|
2567
|
+
}
|
|
2568
|
+
baseRadius() {
|
|
2569
|
+
var _a, _b, _c, _d;
|
|
2570
|
+
const extent = this.graph.__data__.extremes.x_max || 1;
|
|
2571
|
+
const sizeScale = ((_a = this.__data__.meta) == null ? void 0 : _a.sizeScale) ?? 1;
|
|
2572
|
+
if ((_b = this.__data__.meta) == null ? void 0 : _b.fixedSize) {
|
|
2573
|
+
const rect = (_d = (_c = this.graph.svg_element) == null ? void 0 : _c.getBoundingClientRect) == null ? void 0 : _d.call(_c);
|
|
2574
|
+
const zoom = this.graph.zoom_level || 1;
|
|
2575
|
+
const pxPerUnit = rect && rect.width > 0 ? rect.width / (2 * extent) : 1;
|
|
2576
|
+
const targetPx = this.__data__.size * 2 * sizeScale;
|
|
2577
|
+
return targetPx / pxPerUnit / zoom;
|
|
2578
|
+
}
|
|
2579
|
+
return extent * POINT_RADIUS_PER_EXTENT * this.__data__.size * sizeScale;
|
|
2472
2580
|
}
|
|
2473
2581
|
/** Optional halo stroke (e.g. white outline on unit points to separate
|
|
2474
2582
|
* overlapping dots in dense clusters). Width scales with the dot so it holds
|
|
@@ -2484,6 +2592,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2484
2592
|
const tag = this.isSquare ? "rect" : "circle";
|
|
2485
2593
|
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
2486
2594
|
el.setAttribute("data-point-id", this.__data__.ID);
|
|
2595
|
+
const meta = this.__data__.meta ?? {};
|
|
2596
|
+
if (meta.kind) el.setAttribute("data-kind", meta.kind);
|
|
2597
|
+
if (meta.name != null) el.setAttribute("data-name", String(meta.name));
|
|
2598
|
+
if (meta.group != null) el.setAttribute("data-group", String(meta.group));
|
|
2599
|
+
if (meta.unit != null) el.setAttribute("data-unit", String(meta.unit));
|
|
2487
2600
|
el.setAttribute("fill", this.__data__.color.toString());
|
|
2488
2601
|
this.applyStroke(el);
|
|
2489
2602
|
const r = this.computeRadius();
|
|
@@ -2498,9 +2611,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2498
2611
|
el.setAttribute("r", r.toString());
|
|
2499
2612
|
}
|
|
2500
2613
|
this.svg_element = el;
|
|
2501
|
-
el.addEventListener("click", (
|
|
2502
|
-
console.log("Point clicked: ", this.__data__);
|
|
2503
|
-
event.stopPropagation();
|
|
2614
|
+
el.addEventListener("click", () => {
|
|
2504
2615
|
this.__data__.active = !this.__data__.active;
|
|
2505
2616
|
this.update();
|
|
2506
2617
|
const pointEvent = new CustomEvent("point-clicked", {
|
|
@@ -2847,6 +2958,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2847
2958
|
this.customX = customX;
|
|
2848
2959
|
this.customY = customY;
|
|
2849
2960
|
this._visible = false;
|
|
2961
|
+
this._offsetX = 0;
|
|
2962
|
+
this._offsetY = 0;
|
|
2963
|
+
this.pinned = false;
|
|
2850
2964
|
this.svg_element = this._create();
|
|
2851
2965
|
if (displayMode === Label.DISPLAY_TYPES.ON) {
|
|
2852
2966
|
this._visible = true;
|
|
@@ -2860,10 +2974,52 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2860
2974
|
const el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
2861
2975
|
el.textContent = this.text;
|
|
2862
2976
|
el.setAttribute("data-label-for", this.point.ID);
|
|
2863
|
-
el.setAttribute("pointer-events", "
|
|
2977
|
+
el.setAttribute("pointer-events", "auto");
|
|
2978
|
+
el.style.cursor = "grab";
|
|
2979
|
+
this._attachDrag(el);
|
|
2864
2980
|
this.parent.appendChild(el);
|
|
2865
2981
|
return el;
|
|
2866
2982
|
}
|
|
2983
|
+
/** Drag to reposition; pins the label open and emits qe-label-move on drop. */
|
|
2984
|
+
_attachDrag(el) {
|
|
2985
|
+
el.addEventListener("mousedown", (ev) => {
|
|
2986
|
+
ev.preventDefault();
|
|
2987
|
+
ev.stopPropagation();
|
|
2988
|
+
const svg = el.ownerSVGElement;
|
|
2989
|
+
if (!svg) return;
|
|
2990
|
+
const toUser = /* @__PURE__ */ __name((cx, cy) => {
|
|
2991
|
+
const p = svg.createSVGPoint();
|
|
2992
|
+
p.x = cx;
|
|
2993
|
+
p.y = cy;
|
|
2994
|
+
const m = svg.getScreenCTM();
|
|
2995
|
+
return m ? p.matrixTransform(m.inverse()) : { x: 0, y: 0 };
|
|
2996
|
+
}, "toUser");
|
|
2997
|
+
const start = toUser(ev.clientX, ev.clientY);
|
|
2998
|
+
const baseX = this._offsetX, baseY = this._offsetY;
|
|
2999
|
+
el.style.cursor = "grabbing";
|
|
3000
|
+
const onMove = /* @__PURE__ */ __name((e) => {
|
|
3001
|
+
const p = toUser(e.clientX, e.clientY);
|
|
3002
|
+
this._offsetX = baseX + (p.x - start.x);
|
|
3003
|
+
this._offsetY = baseY + (p.y - start.y);
|
|
3004
|
+
this.pinned = true;
|
|
3005
|
+
this._visible = true;
|
|
3006
|
+
this.svg_element.setAttribute("display", "block");
|
|
3007
|
+
this.update();
|
|
3008
|
+
}, "onMove");
|
|
3009
|
+
const onUp = /* @__PURE__ */ __name(() => {
|
|
3010
|
+
document.removeEventListener("mousemove", onMove);
|
|
3011
|
+
document.removeEventListener("mouseup", onUp);
|
|
3012
|
+
el.style.cursor = "grab";
|
|
3013
|
+
this.graph.dispatchEvent(new CustomEvent("qe-label-move", {
|
|
3014
|
+
detail: { id: this.point.ID, transform: { x: this._offsetX, y: this._offsetY } },
|
|
3015
|
+
bubbles: true,
|
|
3016
|
+
composed: true
|
|
3017
|
+
}));
|
|
3018
|
+
}, "onUp");
|
|
3019
|
+
document.addEventListener("mousemove", onMove);
|
|
3020
|
+
document.addEventListener("mouseup", onUp);
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
2867
3023
|
/**
|
|
2868
3024
|
* Recompute position and font-size from current graph extents.
|
|
2869
3025
|
* No-ops when the label is hidden (AUTO/CLICK modes while not hovered/clicked).
|
|
@@ -2912,6 +3068,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2912
3068
|
}
|
|
2913
3069
|
this.svg_element.setAttribute("x", x.toString());
|
|
2914
3070
|
this.svg_element.setAttribute("y", y.toString());
|
|
3071
|
+
if (this._offsetX || this._offsetY) {
|
|
3072
|
+
this.svg_element.setAttribute("transform", `translate(${this._offsetX}, ${this._offsetY})`);
|
|
3073
|
+
}
|
|
2915
3074
|
this.svg_element.setAttribute("font-size", fontSize.toString());
|
|
2916
3075
|
this.svg_element.setAttribute("font-family", "system-ui, sans-serif");
|
|
2917
3076
|
this.svg_element.setAttribute("font-weight", "500");
|
|
@@ -2925,6 +3084,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
2925
3084
|
this.update();
|
|
2926
3085
|
}
|
|
2927
3086
|
hide() {
|
|
3087
|
+
if (this.pinned) return;
|
|
2928
3088
|
this._visible = false;
|
|
2929
3089
|
this.svg_element.setAttribute("display", "none");
|
|
2930
3090
|
}
|
|
@@ -3007,6 +3167,14 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3007
3167
|
return { halfW: t95 * seX, halfH: t95 * seY };
|
|
3008
3168
|
}
|
|
3009
3169
|
__name(computeCIRect, "computeCIRect");
|
|
3170
|
+
const MEAN_MAX_FRACTION_OF_INTERVAL = 0.5;
|
|
3171
|
+
function capMeanToInterval(meanPt, halfW, halfH) {
|
|
3172
|
+
if (!meanPt) return;
|
|
3173
|
+
const half = Math.min(Math.abs(halfW), Math.abs(halfH));
|
|
3174
|
+
if (!(half > 0)) return;
|
|
3175
|
+
meanPt.meta = { ...meanPt.meta ?? {}, maxRadius: half * MEAN_MAX_FRACTION_OF_INTERVAL };
|
|
3176
|
+
}
|
|
3177
|
+
__name(capMeanToInterval, "capMeanToInterval");
|
|
3010
3178
|
function readIntervalBounds(row, dim0, dim1) {
|
|
3011
3179
|
const get = /* @__PURE__ */ __name((col) => {
|
|
3012
3180
|
const i = row.names.indexOf(col);
|
|
@@ -3020,6 +3188,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3020
3188
|
return { x_low: xLow, x_high: xHigh, y_low: yLow, y_high: yHigh };
|
|
3021
3189
|
}
|
|
3022
3190
|
__name(readIntervalBounds, "readIntervalBounds");
|
|
3191
|
+
function parseColorScale(attr) {
|
|
3192
|
+
const a = attr == null ? void 0 : attr.trim().toLowerCase();
|
|
3193
|
+
return a === "plot" || a === "absolute" ? a : "model";
|
|
3194
|
+
}
|
|
3195
|
+
__name(parseColorScale, "parseColorScale");
|
|
3023
3196
|
function parseLabelMode(attr, defaultMode) {
|
|
3024
3197
|
switch (attr == null ? void 0 : attr.toLowerCase()) {
|
|
3025
3198
|
case "on":
|
|
@@ -3053,6 +3226,13 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3053
3226
|
this._axisXText = null;
|
|
3054
3227
|
this._axisYText = null;
|
|
3055
3228
|
this._axisLabelEls = /* @__PURE__ */ new Map();
|
|
3229
|
+
this._magnifyText = null;
|
|
3230
|
+
this._magnifyEl = null;
|
|
3231
|
+
this._animateMs = 0;
|
|
3232
|
+
this._tweenRaf = null;
|
|
3233
|
+
this._highlight = { edges: /* @__PURE__ */ new Set(), nodes: /* @__PURE__ */ new Set() };
|
|
3234
|
+
this._highlightGroup = null;
|
|
3235
|
+
this._tooltipEl = null;
|
|
3056
3236
|
this.graph = this;
|
|
3057
3237
|
this.parent = null;
|
|
3058
3238
|
this.__data__ = new Graph(DefaultGraphOptions);
|
|
@@ -3064,6 +3244,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3064
3244
|
if (this._vizena && e.target !== this._vizena) return;
|
|
3065
3245
|
const model = e.detail;
|
|
3066
3246
|
if (!model) return;
|
|
3247
|
+
this._animateMs = this.hasAttribute("animate") ? parseInt(this.getAttribute("animate") || "", 10) || 300 : 0;
|
|
3248
|
+
const tweenFrom = this._animateMs > 0 && this._hasModel ? this._captureTween() : null;
|
|
3067
3249
|
this._hasModel = true;
|
|
3068
3250
|
const dims = model.dimensions;
|
|
3069
3251
|
const unitIdCol = model.unit_id_col;
|
|
@@ -3072,6 +3254,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3072
3254
|
const config = this._readLayerConfig();
|
|
3073
3255
|
this._showAxes = config.showAxes;
|
|
3074
3256
|
this._axisXText = config.axisXLabel === null ? null : config.axisXLabel || String((dims == null ? void 0 : dims[0]) ?? "x");
|
|
3257
|
+
this._magnifyText = config.edgeMagnify == null ? null : `(scaled ${config.edgeMagnify.toFixed(1)}x)`;
|
|
3075
3258
|
this._axisYText = config.axisYLabel === null ? null : config.axisYLabel || String((dims == null ? void 0 : dims[1]) ?? "y");
|
|
3076
3259
|
const groupColorMap = {};
|
|
3077
3260
|
if (groupColName && ((_a = model.points) == null ? void 0 : _a.names)) {
|
|
@@ -3140,8 +3323,16 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3140
3323
|
const colorStr = group ? groupColorMap[group] ?? "#888888" : "#888888";
|
|
3141
3324
|
if (showUnitPoints) {
|
|
3142
3325
|
const pt = rowToPoint(row, unitIdCol, colorStr, pointScale);
|
|
3143
|
-
pt.size = 2.5;
|
|
3144
|
-
pt.meta = {
|
|
3326
|
+
pt.size = config.pointSize ?? 2.5;
|
|
3327
|
+
pt.meta = {
|
|
3328
|
+
...pt.meta ?? {},
|
|
3329
|
+
stroke: "#ffffff",
|
|
3330
|
+
kind: "point",
|
|
3331
|
+
name: pt.ID,
|
|
3332
|
+
group: group ?? void 0,
|
|
3333
|
+
unit: pt.ID,
|
|
3334
|
+
fixedSize: config.pointFixedSize
|
|
3335
|
+
};
|
|
3145
3336
|
this.__data__.points.set(pt.ID, pt);
|
|
3146
3337
|
pointsLayer.__data__.objects.set(pt.ID, pt);
|
|
3147
3338
|
}
|
|
@@ -3191,19 +3382,26 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3191
3382
|
const meanPt = new Point({
|
|
3192
3383
|
ID: `mean-${group}`,
|
|
3193
3384
|
dimensions: [mx, my],
|
|
3194
|
-
meta: { group }
|
|
3385
|
+
meta: { kind: "mean", name: group, group, fixedSize: config.meanFixedSize }
|
|
3195
3386
|
});
|
|
3196
3387
|
meanPt.color = new Color(color);
|
|
3197
3388
|
meanPt.size = 4;
|
|
3198
3389
|
meanPt.shape = "rect";
|
|
3199
3390
|
this.__data__.points.set(meanPt.ID, meanPt);
|
|
3200
3391
|
pointsLayer.__data__.objects.set(meanPt.ID, meanPt);
|
|
3201
|
-
const bounds = readIntervalBounds(row, dims[0], dims[1]);
|
|
3202
|
-
if (bounds) {
|
|
3392
|
+
const bounds = config.showCI ? readIntervalBounds(row, dims[0], dims[1]) : null;
|
|
3393
|
+
if (!bounds) {
|
|
3394
|
+
const stale = this._ciRects.get(group);
|
|
3395
|
+
if (stale) {
|
|
3396
|
+
stale.remove();
|
|
3397
|
+
this._ciRects.delete(group);
|
|
3398
|
+
}
|
|
3399
|
+
} else {
|
|
3203
3400
|
const rect_x = bounds.x_low * pointScale;
|
|
3204
3401
|
const rect_y = -bounds.y_high * pointScale;
|
|
3205
3402
|
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
3206
3403
|
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
3404
|
+
capMeanToInterval(meanPt, rect_width / 2, rect_height / 2);
|
|
3207
3405
|
let el = this._ciRects.get(group);
|
|
3208
3406
|
if (!el) {
|
|
3209
3407
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -3229,7 +3427,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3229
3427
|
const meanPt = new Point({
|
|
3230
3428
|
ID: `mean-${group}`,
|
|
3231
3429
|
dimensions: [x / n * pointScale, -(y / n) * pointScale],
|
|
3232
|
-
meta: { group }
|
|
3430
|
+
meta: { kind: "mean", name: group, group, fixedSize: config.meanFixedSize }
|
|
3233
3431
|
});
|
|
3234
3432
|
meanPt.color = new Color(color);
|
|
3235
3433
|
meanPt.size = 4;
|
|
@@ -3257,6 +3455,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3257
3455
|
const rect_y = -bounds.y_high * pointScale;
|
|
3258
3456
|
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
3259
3457
|
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
3458
|
+
capMeanToInterval(
|
|
3459
|
+
this.__data__.points.get(`mean-${group}`),
|
|
3460
|
+
rect_width / 2,
|
|
3461
|
+
rect_height / 2
|
|
3462
|
+
);
|
|
3260
3463
|
let el = this._ciRects.get(group);
|
|
3261
3464
|
if (!el) {
|
|
3262
3465
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -3284,6 +3487,11 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3284
3487
|
const rawVarY = y2 / n - μy * μy;
|
|
3285
3488
|
const { halfW, halfH } = computeCIRect(rawVarX, rawVarY, n, pointScale);
|
|
3286
3489
|
const cx = μx * pointScale, cy = -μy * pointScale;
|
|
3490
|
+
capMeanToInterval(
|
|
3491
|
+
this.__data__.points.get(`mean-${group}`),
|
|
3492
|
+
halfW,
|
|
3493
|
+
halfH
|
|
3494
|
+
);
|
|
3287
3495
|
let el = this._ciRects.get(group);
|
|
3288
3496
|
if (!el) {
|
|
3289
3497
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -3456,6 +3664,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3456
3664
|
model.nodes.rows().forEach((row) => {
|
|
3457
3665
|
const pt = rowToPoint(row, nodeIdCol, "#444444");
|
|
3458
3666
|
pt.size = 2;
|
|
3667
|
+
pt.meta = { ...pt.meta ?? {}, kind: "node", name: pt.ID };
|
|
3459
3668
|
nodeLayer.__data__.objects.set(pt.ID, pt);
|
|
3460
3669
|
});
|
|
3461
3670
|
if (edgesEnabled && edgeLayer && ((_l = model.edges) == null ? void 0 : _l.rows)) {
|
|
@@ -3554,21 +3763,46 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3554
3763
|
});
|
|
3555
3764
|
}
|
|
3556
3765
|
const isDirected = !!model.directed;
|
|
3557
|
-
const
|
|
3558
|
-
const
|
|
3559
|
-
|
|
3560
|
-
|
|
3766
|
+
const widthFrac = widthPerWeight(config.edgeWidth) * (config.edgeMagnify ?? 1);
|
|
3767
|
+
const rangeOf = /* @__PURE__ */ __name((ws) => {
|
|
3768
|
+
const nz = ws.map(Math.abs).filter((v) => v > 0);
|
|
3769
|
+
return nz.length ? [Math.min(...nz), Math.max(...nz)] : [0, 0];
|
|
3770
|
+
}, "rangeOf");
|
|
3771
|
+
const modelRange = /* @__PURE__ */ __name((self2) => {
|
|
3772
|
+
const vals = [];
|
|
3773
|
+
edgePairs.forEach(({ col, pair }) => {
|
|
3774
|
+
if (pair[0] === pair[1] !== self2) return;
|
|
3775
|
+
const column = model.edges.col(col);
|
|
3776
|
+
if (column) for (const v of column) vals.push(Number(v));
|
|
3777
|
+
});
|
|
3778
|
+
return rangeOf(vals);
|
|
3779
|
+
}, "modelRange");
|
|
3780
|
+
const plotRange = /* @__PURE__ */ __name((self2) => rangeOf(drawEdges.filter((e2) => e2.pair[0] === e2.pair[1] === self2).map((e2) => e2.weight)), "plotRange");
|
|
3781
|
+
const cscale = config.edgeColorScale;
|
|
3782
|
+
const colourRange = /* @__PURE__ */ __name((self2) => cscale === "plot" ? plotRange(self2) : modelRange(self2), "colourRange");
|
|
3783
|
+
const lineRange = colourRange(false), selfRange = colourRange(true);
|
|
3784
|
+
const colourOf = /* @__PURE__ */ __name((w, self2) => {
|
|
3785
|
+
if (cscale === "absolute") {
|
|
3786
|
+
const a = Math.min(1, Math.abs(w));
|
|
3787
|
+
return { saturation: a, opacity: a };
|
|
3788
|
+
}
|
|
3789
|
+
const [lo, hi] = self2 ? selfRange : lineRange;
|
|
3790
|
+
return { saturation: rampFor(w, lo, hi, SATURATION_RANGE), opacity: rampFor(w, lo, hi, OPACITY_RANGE) };
|
|
3791
|
+
}, "colourOf");
|
|
3561
3792
|
const nodeWeightSum = {};
|
|
3562
3793
|
drawEdges.forEach(({ pair, weight }) => {
|
|
3563
3794
|
const isSelf = pair[0] === pair[1];
|
|
3564
3795
|
if (isSelf && !isDirected) return;
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3796
|
+
const add = /* @__PURE__ */ __name((id) => {
|
|
3797
|
+
nodeWeightSum[id] = (nodeWeightSum[id] ?? 0) + Math.abs(weight);
|
|
3798
|
+
}, "add");
|
|
3799
|
+
if (isDirected) {
|
|
3800
|
+
add(pair[1]);
|
|
3801
|
+
return;
|
|
3568
3802
|
}
|
|
3803
|
+
add(pair[0]);
|
|
3804
|
+
add(pair[1]);
|
|
3569
3805
|
});
|
|
3570
|
-
const maxNodeWeight = Math.max(1, ...Object.values(nodeWeightSum));
|
|
3571
|
-
const nodeSizes = {};
|
|
3572
3806
|
model.nodes.rows().forEach((row) => {
|
|
3573
3807
|
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3574
3808
|
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
@@ -3576,8 +3810,12 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3576
3810
|
const pt = nodeLayer.__data__.objects.get(id);
|
|
3577
3811
|
if (!pt) return;
|
|
3578
3812
|
const w = nodeWeightSum[id] ?? 0;
|
|
3579
|
-
|
|
3580
|
-
|
|
3813
|
+
const unconnected = !(w > 0);
|
|
3814
|
+
pt.size = unconnected ? config.hideUnconnectedNodes ? 0 : UNCONNECTED_NODE_SIZE : nodeSizeForEdges(w, widthFrac);
|
|
3815
|
+
pt.meta = {
|
|
3816
|
+
...pt.meta ?? {},
|
|
3817
|
+
hidden: unconnected && config.hideUnconnectedNodes
|
|
3818
|
+
};
|
|
3581
3819
|
});
|
|
3582
3820
|
const totalWeightByPair = {};
|
|
3583
3821
|
if (isDirected) {
|
|
@@ -3592,8 +3830,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3592
3830
|
if (!fromNode || !toNode) return;
|
|
3593
3831
|
const isSelf = pair[0] === pair[1];
|
|
3594
3832
|
if (isSelf && !isDirected) return;
|
|
3595
|
-
const
|
|
3596
|
-
const opacity = Math.min(1, weight / classMax);
|
|
3833
|
+
const { saturation, opacity } = colourOf(weight, isSelf);
|
|
3597
3834
|
const fromPt = new Point({ ID: `${pair[0]}-src`, dimensions: [fromNode.x, fromNode.y], meta: {} });
|
|
3598
3835
|
const toPt = new Point({ ID: `${pair[1]}-dst`, dimensions: [toNode.x, toNode.y], meta: {} });
|
|
3599
3836
|
let edgeLine;
|
|
@@ -3617,16 +3854,17 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3617
3854
|
edgeLine.meta = {
|
|
3618
3855
|
isMeanEdge: true,
|
|
3619
3856
|
weight,
|
|
3620
|
-
//
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
maxWeight: isSelf ? maxSelfWeight : maxEdgeWeight,
|
|
3857
|
+
// Width per unit weight (extent units), shared by all marks.
|
|
3858
|
+
widthFrac,
|
|
3859
|
+
saturation,
|
|
3624
3860
|
opacity,
|
|
3625
3861
|
isDirected,
|
|
3626
3862
|
isSelf,
|
|
3627
|
-
//
|
|
3628
|
-
|
|
3629
|
-
|
|
3863
|
+
// Identity for the qe-edge-* interaction events.
|
|
3864
|
+
source: pair[0],
|
|
3865
|
+
target: pair[1],
|
|
3866
|
+
group: groupAttr ?? void 0,
|
|
3867
|
+
unit: unitAttr ?? void 0
|
|
3630
3868
|
};
|
|
3631
3869
|
const targetLayer = isSelf ? edgenodeLayer ?? edgeLayer : edgeLayer;
|
|
3632
3870
|
targetLayer.__data__.objects.set(edgeLine.ID, edgeLine);
|
|
@@ -3634,7 +3872,15 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3634
3872
|
edgeLayer.__data__.__state__.setVariable("layer_updated", edgeLayer.__data__);
|
|
3635
3873
|
if (edgenodeLayer) edgenodeLayer.__data__.__state__.setVariable("layer_updated", edgenodeLayer.__data__);
|
|
3636
3874
|
}
|
|
3637
|
-
|
|
3875
|
+
const nodesVisible = config.showNodes;
|
|
3876
|
+
if (nodeLayer.svg_element) {
|
|
3877
|
+
nodeLayer.svg_element.style.display = nodesVisible ? "" : "none";
|
|
3878
|
+
}
|
|
3879
|
+
if (!nodesVisible) {
|
|
3880
|
+
this._nodeLabels.forEach((lbl) => lbl.remove());
|
|
3881
|
+
this._nodeLabels.clear();
|
|
3882
|
+
}
|
|
3883
|
+
if (labelSvg && labelNodes !== 0 && nodesVisible) {
|
|
3638
3884
|
this._nodeLabels.forEach((lbl, id) => {
|
|
3639
3885
|
if (!nodeLayer.__data__.objects.get(id)) {
|
|
3640
3886
|
lbl.remove();
|
|
@@ -3642,11 +3888,17 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3642
3888
|
}
|
|
3643
3889
|
});
|
|
3644
3890
|
model.nodes.rows().forEach((row) => {
|
|
3891
|
+
var _a2, _b2;
|
|
3645
3892
|
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3646
3893
|
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
3647
3894
|
if (!id) return;
|
|
3648
3895
|
const pt = nodeLayer.__data__.objects.get(id);
|
|
3649
3896
|
if (!pt) return;
|
|
3897
|
+
if ((_a2 = pt.meta) == null ? void 0 : _a2.hidden) {
|
|
3898
|
+
(_b2 = this._nodeLabels.get(id)) == null ? void 0 : _b2.remove();
|
|
3899
|
+
this._nodeLabels.delete(id);
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3650
3902
|
let lbl = this._nodeLabels.get(id);
|
|
3651
3903
|
if (!lbl) {
|
|
3652
3904
|
lbl = new QELabel(id, pt, labelSvg, this, labelNodes);
|
|
@@ -3663,6 +3915,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3663
3915
|
if (axisPointsLayer) this.__data__.__state__.setVariable("graph_updated_axis_points", axisPointsLayer.__data__.objects);
|
|
3664
3916
|
if (axisLinesLayer) this.__data__.__state__.setVariable("graph_updated_axis_lines", axisLinesLayer.__data__.objects);
|
|
3665
3917
|
this.update();
|
|
3918
|
+
if (tweenFrom) this._runTween(tweenFrom);
|
|
3666
3919
|
});
|
|
3667
3920
|
}
|
|
3668
3921
|
static get observedAttributes() {
|
|
@@ -3703,6 +3956,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3703
3956
|
// should render. Called once at the top of every model-updated handler so
|
|
3704
3957
|
// all rendering decisions are derived from a single snapshot.
|
|
3705
3958
|
_readLayerConfig() {
|
|
3959
|
+
var _a;
|
|
3706
3960
|
const nodesEl = this.querySelector("qe-nodes");
|
|
3707
3961
|
const meansEl = this.querySelector("qe-means");
|
|
3708
3962
|
const pointsEl = this.querySelector("qe-points");
|
|
@@ -3711,11 +3965,18 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3711
3965
|
const axesEl = this.querySelector("qe-axes");
|
|
3712
3966
|
const labelsShorthand = this.getAttribute("labels");
|
|
3713
3967
|
return {
|
|
3714
|
-
// Nodes — always
|
|
3968
|
+
// Nodes — always placed from the model (they anchor the coordinate
|
|
3969
|
+
// space); <qe-nodes> presence draws them, like every other layer.
|
|
3715
3970
|
labelNodes: parseLabelMode(
|
|
3716
3971
|
(nodesEl == null ? void 0 : nodesEl.getAttribute("label")) ?? this.getAttribute("label-nodes") ?? labelsShorthand,
|
|
3717
3972
|
Label.DISPLAY_TYPES.ON
|
|
3718
3973
|
),
|
|
3974
|
+
showNodes: !!nodesEl,
|
|
3975
|
+
edgeWidth: (() => {
|
|
3976
|
+
const v = Number(this.getAttribute("edge-width"));
|
|
3977
|
+
return Number.isFinite(v) && v > 0 && v <= 1 ? v : DEFAULT_EDGE_WIDTH;
|
|
3978
|
+
})(),
|
|
3979
|
+
hideUnconnectedNodes: ((_a = nodesEl == null ? void 0 : nodesEl.getAttribute("unconnected")) == null ? void 0 : _a.toLowerCase()) === "hide",
|
|
3719
3980
|
scalePoints: this.getAttribute("scale-points") !== "false",
|
|
3720
3981
|
// Means — presence of <qe-means> = show; absence = hide.
|
|
3721
3982
|
showMeans: !!meansEl,
|
|
@@ -3725,18 +3986,28 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3725
3986
|
(meansEl == null ? void 0 : meansEl.getAttribute("label")) ?? this.getAttribute("label-means") ?? labelsShorthand,
|
|
3726
3987
|
Label.DISPLAY_TYPES.ON
|
|
3727
3988
|
),
|
|
3989
|
+
// fixed-size keeps mean markers a constant on-screen px size (webENA parity).
|
|
3990
|
+
meanFixedSize: !!meansEl && meansEl.hasAttribute("fixed-size"),
|
|
3728
3991
|
// Points — presence of <qe-points> = show unit dots.
|
|
3729
3992
|
showPoints: !!pointsEl,
|
|
3730
3993
|
labelPoints: parseLabelMode(
|
|
3731
3994
|
(pointsEl == null ? void 0 : pointsEl.getAttribute("label")) ?? this.getAttribute("label-points") ?? labelsShorthand,
|
|
3732
3995
|
Label.DISPLAY_TYPES.OFF
|
|
3733
3996
|
),
|
|
3997
|
+
pointSize: (pointsEl == null ? void 0 : pointsEl.getAttribute("size")) ? parseFloat(pointsEl.getAttribute("size")) : null,
|
|
3998
|
+
// fixed-size keeps unit dots a constant on-screen px size (webENA parity).
|
|
3999
|
+
pointFixedSize: !!pointsEl && pointsEl.hasAttribute("fixed-size"),
|
|
3734
4000
|
// Edges — presence of <qe-edges> = draw a network.
|
|
3735
4001
|
showEdges: !!edgesEl,
|
|
3736
4002
|
edgeGroup: (edgesEl == null ? void 0 : edgesEl.getAttribute("group")) ?? null,
|
|
3737
4003
|
edgeUnit: (edgesEl == null ? void 0 : edgesEl.getAttribute("unit")) ?? null,
|
|
3738
4004
|
edgeCompare: (edgesEl == null ? void 0 : edgesEl.getAttribute("compare")) ?? null,
|
|
3739
4005
|
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null,
|
|
4006
|
+
edgeColorScale: parseColorScale((edgesEl == null ? void 0 : edgesEl.getAttribute("color-scale")) ?? null),
|
|
4007
|
+
edgeMagnify: (edgesEl == null ? void 0 : edgesEl.hasAttribute("magnify")) ? (() => {
|
|
4008
|
+
const v = Number(edgesEl.getAttribute("magnify"));
|
|
4009
|
+
return Number.isFinite(v) && v > 0 ? v : 1;
|
|
4010
|
+
})() : null,
|
|
3740
4011
|
// Trajectories — presence of <qe-trajectories> = draw cognitive trajectories
|
|
3741
4012
|
showTrajectories: !!trajectoriesEl,
|
|
3742
4013
|
trajectoryUnits: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("units")) ?? null,
|
|
@@ -3777,6 +4048,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3777
4048
|
if (w) this.style.width = isNaN(Number(w)) ? w : `${w}px`;
|
|
3778
4049
|
if (h) this.style.height = isNaN(Number(h)) ? h : `${h}px`;
|
|
3779
4050
|
this.appendChild(this.svg_element);
|
|
4051
|
+
_QEGraphElement._instances.add(this);
|
|
3780
4052
|
this._childObserver = new MutationObserver(() => this._scheduleRerender());
|
|
3781
4053
|
this._childObserver.observe(this, {
|
|
3782
4054
|
childList: true,
|
|
@@ -3787,10 +4059,14 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3787
4059
|
"unit",
|
|
3788
4060
|
"compare",
|
|
3789
4061
|
"also",
|
|
4062
|
+
"color-scale",
|
|
4063
|
+
"magnify",
|
|
3790
4064
|
"groups",
|
|
3791
4065
|
"confidence",
|
|
3792
4066
|
"label",
|
|
4067
|
+
"unconnected",
|
|
3793
4068
|
"scale-points",
|
|
4069
|
+
"edge-width",
|
|
3794
4070
|
"labels",
|
|
3795
4071
|
"label-nodes",
|
|
3796
4072
|
"label-means",
|
|
@@ -3806,7 +4082,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3806
4082
|
"stroke-width-range",
|
|
3807
4083
|
"opacity-range",
|
|
3808
4084
|
"x",
|
|
3809
|
-
"y"
|
|
4085
|
+
"y",
|
|
4086
|
+
"size",
|
|
4087
|
+
"fixed-size"
|
|
3810
4088
|
]
|
|
3811
4089
|
});
|
|
3812
4090
|
const forId = this.getAttribute("for");
|
|
@@ -3826,9 +4104,275 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3826
4104
|
}
|
|
3827
4105
|
}
|
|
3828
4106
|
disconnectedCallback() {
|
|
3829
|
-
var _a;
|
|
4107
|
+
var _a, _b;
|
|
3830
4108
|
(_a = this._childObserver) == null ? void 0 : _a.disconnect();
|
|
3831
4109
|
this._childObserver = null;
|
|
4110
|
+
_QEGraphElement._instances.delete(this);
|
|
4111
|
+
(_b = this._tooltipEl) == null ? void 0 : _b.remove();
|
|
4112
|
+
this._tooltipEl = null;
|
|
4113
|
+
}
|
|
4114
|
+
/** All connected <qe-graph> instances (newest membership order). */
|
|
4115
|
+
static instances() {
|
|
4116
|
+
return [..._QEGraphElement._instances];
|
|
4117
|
+
}
|
|
4118
|
+
/** Look up a graph by its `id` or `plot-group` attribute. */
|
|
4119
|
+
static getGraph(key) {
|
|
4120
|
+
for (const g of _QEGraphElement._instances) {
|
|
4121
|
+
if (g.id === key || g.getAttribute("plot-group") === key) return g;
|
|
4122
|
+
}
|
|
4123
|
+
return null;
|
|
4124
|
+
}
|
|
4125
|
+
/** Resolved code nodes with their current graph-space coordinates. */
|
|
4126
|
+
getNodes() {
|
|
4127
|
+
const layer = this.__children__.search("__children__", "nodes");
|
|
4128
|
+
if (!layer) return [];
|
|
4129
|
+
return [...layer.__data__.objects.values()].map((o) => {
|
|
4130
|
+
var _a;
|
|
4131
|
+
return {
|
|
4132
|
+
id: o.ID,
|
|
4133
|
+
name: ((_a = o.meta) == null ? void 0 : _a.name) ?? o.ID,
|
|
4134
|
+
x: o.x,
|
|
4135
|
+
y: o.y
|
|
4136
|
+
};
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
/** Resolved edges of the current network with weights and direction. */
|
|
4140
|
+
getEdges() {
|
|
4141
|
+
const collect = /* @__PURE__ */ __name((name) => {
|
|
4142
|
+
const l = this.__children__.search("__children__", name);
|
|
4143
|
+
return l ? [...l.__data__.objects.values()] : [];
|
|
4144
|
+
}, "collect");
|
|
4145
|
+
return [...collect("edges"), ...collect("edgenodes")].map((o) => {
|
|
4146
|
+
const m = o.meta ?? {};
|
|
4147
|
+
return {
|
|
4148
|
+
source: m.source,
|
|
4149
|
+
target: m.target,
|
|
4150
|
+
weight: m.weight ?? 0,
|
|
4151
|
+
directed: !!m.isDirected,
|
|
4152
|
+
self: !!m.isSelf
|
|
4153
|
+
};
|
|
4154
|
+
}).filter((e) => e.source != null && e.target != null);
|
|
4155
|
+
}
|
|
4156
|
+
/** Marks (circles/rects), undirected edge lines, and directed / self-edge
|
|
4157
|
+
* triangle paths all participate in tweening. */
|
|
4158
|
+
_tweenTargets() {
|
|
4159
|
+
return [...this.svg_element.querySelectorAll(
|
|
4160
|
+
"circle[data-point-id], rect[data-point-id], line[data-edge-source], path[data-edge-source]"
|
|
4161
|
+
)];
|
|
4162
|
+
}
|
|
4163
|
+
_readCoords(el) {
|
|
4164
|
+
const tag = el.tagName.toLowerCase();
|
|
4165
|
+
const n = /* @__PURE__ */ __name((a) => parseFloat(el.getAttribute(a) || "0"), "n");
|
|
4166
|
+
if (tag === "circle") return { t: "c", cx: n("cx"), cy: n("cy") };
|
|
4167
|
+
if (tag === "rect") return { t: "r", x: n("x"), y: n("y") };
|
|
4168
|
+
if (tag === "line") return { t: "l", x1: n("x1"), y1: n("y1"), x2: n("x2"), y2: n("y2") };
|
|
4169
|
+
if (tag === "path") {
|
|
4170
|
+
const d = el.getAttribute("d") || "";
|
|
4171
|
+
return { t: "p", d, nums: (d.match(_QEGraphElement._PATH_NUM_RE) || []).map(Number) };
|
|
4172
|
+
}
|
|
4173
|
+
return null;
|
|
4174
|
+
}
|
|
4175
|
+
/** Snapshot current tween-target positions keyed by element. */
|
|
4176
|
+
_captureTween() {
|
|
4177
|
+
const m = /* @__PURE__ */ new Map();
|
|
4178
|
+
this._tweenTargets().forEach((el) => {
|
|
4179
|
+
const c = this._readCoords(el);
|
|
4180
|
+
if (c) m.set(el, c);
|
|
4181
|
+
});
|
|
4182
|
+
return m;
|
|
4183
|
+
}
|
|
4184
|
+
/** Animate each still-present element from its snapshot to its new position. */
|
|
4185
|
+
_runTween(from) {
|
|
4186
|
+
if (this._tweenRaf !== null) {
|
|
4187
|
+
cancelAnimationFrame(this._tweenRaf);
|
|
4188
|
+
this._tweenRaf = null;
|
|
4189
|
+
}
|
|
4190
|
+
const to = /* @__PURE__ */ new Map();
|
|
4191
|
+
this._tweenTargets().forEach((el) => {
|
|
4192
|
+
if (from.has(el)) {
|
|
4193
|
+
const c = this._readCoords(el);
|
|
4194
|
+
if (c) to.set(el, c);
|
|
4195
|
+
}
|
|
4196
|
+
});
|
|
4197
|
+
if (!to.size) return;
|
|
4198
|
+
const dur = this._animateMs;
|
|
4199
|
+
const t0 = performance.now();
|
|
4200
|
+
const ease = /* @__PURE__ */ __name((t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2, "ease");
|
|
4201
|
+
const lerp = /* @__PURE__ */ __name((a, b, k) => a + (b - a) * k, "lerp");
|
|
4202
|
+
const step = /* @__PURE__ */ __name((now) => {
|
|
4203
|
+
const t = Math.min(1, (now - t0) / dur);
|
|
4204
|
+
const k = ease(t);
|
|
4205
|
+
to.forEach((nc, el) => {
|
|
4206
|
+
const oc = from.get(el);
|
|
4207
|
+
if (!oc || oc.t !== nc.t) return;
|
|
4208
|
+
if (nc.t === "c") {
|
|
4209
|
+
el.setAttribute("cx", String(lerp(oc.cx, nc.cx, k)));
|
|
4210
|
+
el.setAttribute("cy", String(lerp(oc.cy, nc.cy, k)));
|
|
4211
|
+
} else if (nc.t === "r") {
|
|
4212
|
+
el.setAttribute("x", String(lerp(oc.x, nc.x, k)));
|
|
4213
|
+
el.setAttribute("y", String(lerp(oc.y, nc.y, k)));
|
|
4214
|
+
} else if (nc.t === "l") {
|
|
4215
|
+
el.setAttribute("x1", String(lerp(oc.x1, nc.x1, k)));
|
|
4216
|
+
el.setAttribute("y1", String(lerp(oc.y1, nc.y1, k)));
|
|
4217
|
+
el.setAttribute("x2", String(lerp(oc.x2, nc.x2, k)));
|
|
4218
|
+
el.setAttribute("y2", String(lerp(oc.y2, nc.y2, k)));
|
|
4219
|
+
} else {
|
|
4220
|
+
if (!oc.nums || oc.nums.length !== nc.nums.length) return;
|
|
4221
|
+
let i = 0;
|
|
4222
|
+
const d = nc.d.replace(
|
|
4223
|
+
_QEGraphElement._PATH_NUM_RE,
|
|
4224
|
+
() => String(lerp(oc.nums[i], nc.nums[i++], k))
|
|
4225
|
+
);
|
|
4226
|
+
el.setAttribute("d", d);
|
|
4227
|
+
}
|
|
4228
|
+
});
|
|
4229
|
+
this._tweenRaf = t < 1 ? requestAnimationFrame(step) : null;
|
|
4230
|
+
}, "step");
|
|
4231
|
+
this._tweenRaf = requestAnimationFrame(step);
|
|
4232
|
+
}
|
|
4233
|
+
/** Serialize the current plot as a standalone SVG string (webENA P2.3). */
|
|
4234
|
+
toSVG() {
|
|
4235
|
+
const svg = this.svg_element.cloneNode(true);
|
|
4236
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
4237
|
+
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
4238
|
+
const rect = this.svg_element.getBoundingClientRect();
|
|
4239
|
+
if (rect.width) svg.setAttribute("width", String(Math.round(rect.width)));
|
|
4240
|
+
if (rect.height) svg.setAttribute("height", String(Math.round(rect.height)));
|
|
4241
|
+
return new XMLSerializer().serializeToString(svg);
|
|
4242
|
+
}
|
|
4243
|
+
/** Rasterize the current plot to a PNG data URL (webENA P2.3). `scale`
|
|
4244
|
+
* multiplies the pixel dimensions for a higher-resolution image. */
|
|
4245
|
+
toPNG(scale = 2) {
|
|
4246
|
+
const svgStr = this.toSVG();
|
|
4247
|
+
const rect = this.svg_element.getBoundingClientRect();
|
|
4248
|
+
const w = Math.max(1, Math.round(rect.width));
|
|
4249
|
+
const h = Math.max(1, Math.round(rect.height));
|
|
4250
|
+
return new Promise((resolve, reject) => {
|
|
4251
|
+
const blob = new Blob([svgStr], { type: "image/svg+xml;charset=utf-8" });
|
|
4252
|
+
const url = URL.createObjectURL(blob);
|
|
4253
|
+
const img = new Image();
|
|
4254
|
+
img.onload = () => {
|
|
4255
|
+
try {
|
|
4256
|
+
const canvas = document.createElement("canvas");
|
|
4257
|
+
canvas.width = w * scale;
|
|
4258
|
+
canvas.height = h * scale;
|
|
4259
|
+
const ctx = canvas.getContext("2d");
|
|
4260
|
+
if (!ctx) {
|
|
4261
|
+
URL.revokeObjectURL(url);
|
|
4262
|
+
reject(new Error("no 2d context"));
|
|
4263
|
+
return;
|
|
4264
|
+
}
|
|
4265
|
+
ctx.scale(scale, scale);
|
|
4266
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
4267
|
+
URL.revokeObjectURL(url);
|
|
4268
|
+
resolve(canvas.toDataURL("image/png"));
|
|
4269
|
+
} catch (err) {
|
|
4270
|
+
URL.revokeObjectURL(url);
|
|
4271
|
+
reject(err);
|
|
4272
|
+
}
|
|
4273
|
+
};
|
|
4274
|
+
img.onerror = (e) => {
|
|
4275
|
+
URL.revokeObjectURL(url);
|
|
4276
|
+
reject(e);
|
|
4277
|
+
};
|
|
4278
|
+
img.src = url;
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
/** Unit points with their current graph-space coordinates. */
|
|
4282
|
+
getPoints() {
|
|
4283
|
+
const layer = this.__children__.search("__children__", "points");
|
|
4284
|
+
if (!layer) return [];
|
|
4285
|
+
return [...layer.__data__.objects.values()].filter((o) => {
|
|
4286
|
+
var _a;
|
|
4287
|
+
return ((_a = o.meta) == null ? void 0 : _a.kind) === "point";
|
|
4288
|
+
}).map((o) => {
|
|
4289
|
+
var _a;
|
|
4290
|
+
return { id: o.ID, group: (_a = o.meta) == null ? void 0 : _a.group, x: o.x, y: o.y };
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
/** Ids of unit points inside a graph-space rectangle (for drag/lasso select). */
|
|
4294
|
+
pointsInRect(rect) {
|
|
4295
|
+
return this.getPoints().filter((p) => p.x >= rect.x && p.x <= rect.x + rect.w && p.y >= rect.y && p.y <= rect.y + rect.h).map((p) => p.id);
|
|
4296
|
+
}
|
|
4297
|
+
/** Order-independent key so an undirected pair matches either direction. */
|
|
4298
|
+
_edgeKey(a, b) {
|
|
4299
|
+
return a <= b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
4300
|
+
}
|
|
4301
|
+
/** Mark edges/nodes as highlighted. Replaces the previous selection. */
|
|
4302
|
+
setHighlight(sel) {
|
|
4303
|
+
this._highlight.edges = new Set((sel.edges ?? []).map((e) => this._edgeKey(e.source, e.target)));
|
|
4304
|
+
this._highlight.nodes = new Set((sel.nodes ?? []).map(String));
|
|
4305
|
+
this._applyHighlights();
|
|
4306
|
+
}
|
|
4307
|
+
/** Clear all highlighting. */
|
|
4308
|
+
clearHighlight() {
|
|
4309
|
+
this._highlight.edges.clear();
|
|
4310
|
+
this._highlight.nodes.clear();
|
|
4311
|
+
this._applyHighlights();
|
|
4312
|
+
}
|
|
4313
|
+
_applyHighlights() {
|
|
4314
|
+
if (!this.svg_element) return;
|
|
4315
|
+
if (!this._highlightGroup) {
|
|
4316
|
+
this._highlightGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
4317
|
+
this._highlightGroup.setAttribute("data-layer-name", "highlight");
|
|
4318
|
+
this._highlightGroup.setAttribute("pointer-events", "none");
|
|
4319
|
+
}
|
|
4320
|
+
this.svg_element.appendChild(this._highlightGroup);
|
|
4321
|
+
const g = this._highlightGroup;
|
|
4322
|
+
g.textContent = "";
|
|
4323
|
+
const extent = this.__data__.extremes.x_max || 1;
|
|
4324
|
+
const HL = "#f5a623";
|
|
4325
|
+
const mk = /* @__PURE__ */ __name((tag, attrs) => {
|
|
4326
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
4327
|
+
for (const k in attrs) el.setAttribute(k, String(attrs[k]));
|
|
4328
|
+
return el;
|
|
4329
|
+
}, "mk");
|
|
4330
|
+
if (this._highlight.edges.size) {
|
|
4331
|
+
const xy = {};
|
|
4332
|
+
this.getNodes().forEach((n) => {
|
|
4333
|
+
xy[n.id] = { x: n.x, y: n.y };
|
|
4334
|
+
});
|
|
4335
|
+
this._highlight.edges.forEach((key) => {
|
|
4336
|
+
const [a, b] = key.split("\0");
|
|
4337
|
+
const A = xy[a], B = xy[b];
|
|
4338
|
+
if (!A || !B) return;
|
|
4339
|
+
g.appendChild(mk("line", {
|
|
4340
|
+
x1: A.x,
|
|
4341
|
+
y1: A.y,
|
|
4342
|
+
x2: B.x,
|
|
4343
|
+
y2: B.y,
|
|
4344
|
+
stroke: HL,
|
|
4345
|
+
"stroke-width": extent * 0.02,
|
|
4346
|
+
"stroke-opacity": 0.6,
|
|
4347
|
+
"stroke-linecap": "round"
|
|
4348
|
+
}));
|
|
4349
|
+
});
|
|
4350
|
+
}
|
|
4351
|
+
this._highlight.nodes.forEach((id) => {
|
|
4352
|
+
var _a;
|
|
4353
|
+
const el = this.svg_element.querySelector(`[data-point-id="${((_a = window.CSS) == null ? void 0 : _a.escape) ? CSS.escape(id) : id}"]`);
|
|
4354
|
+
if (!el) return;
|
|
4355
|
+
const tag = el.tagName.toLowerCase();
|
|
4356
|
+
let cx, cy, r;
|
|
4357
|
+
if (tag === "rect") {
|
|
4358
|
+
const w = parseFloat(el.getAttribute("width") || "0");
|
|
4359
|
+
cx = parseFloat(el.getAttribute("x") || "0") + w / 2;
|
|
4360
|
+
cy = parseFloat(el.getAttribute("y") || "0") + parseFloat(el.getAttribute("height") || "0") / 2;
|
|
4361
|
+
r = w / 2;
|
|
4362
|
+
} else {
|
|
4363
|
+
cx = parseFloat(el.getAttribute("cx") || "0");
|
|
4364
|
+
cy = parseFloat(el.getAttribute("cy") || "0");
|
|
4365
|
+
r = parseFloat(el.getAttribute("r") || "0");
|
|
4366
|
+
}
|
|
4367
|
+
g.appendChild(mk("circle", {
|
|
4368
|
+
cx,
|
|
4369
|
+
cy,
|
|
4370
|
+
r: r * 1.7 + extent * 4e-3,
|
|
4371
|
+
fill: "none",
|
|
4372
|
+
stroke: HL,
|
|
4373
|
+
"stroke-width": extent * 6e-3
|
|
4374
|
+
}));
|
|
4375
|
+
});
|
|
3832
4376
|
}
|
|
3833
4377
|
create() {
|
|
3834
4378
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
@@ -3946,6 +4490,138 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3946
4490
|
const lbl = getAutoLabel(id);
|
|
3947
4491
|
if ((lbl == null ? void 0 : lbl.displayMode) === Label.DISPLAY_TYPES.AUTO) lbl.hide();
|
|
3948
4492
|
});
|
|
4493
|
+
this._attachInteractionEvents();
|
|
4494
|
+
}
|
|
4495
|
+
/** Emit a namespaced CustomEvent on the public element (bubbles + composed). */
|
|
4496
|
+
_emit(name, detail) {
|
|
4497
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
4498
|
+
}
|
|
4499
|
+
/** Walk up from a hit element to the nearest ancestor carrying `attr`. */
|
|
4500
|
+
_closestWith(el, attr) {
|
|
4501
|
+
var _a, _b;
|
|
4502
|
+
while (el && el !== this.svg_element) {
|
|
4503
|
+
if (((_a = el.getAttribute) == null ? void 0 : _a.call(el, attr)) != null && ((_b = el.hasAttribute) == null ? void 0 : _b.call(el, attr))) return el;
|
|
4504
|
+
el = el.parentElement;
|
|
4505
|
+
}
|
|
4506
|
+
return null;
|
|
4507
|
+
}
|
|
4508
|
+
/** Resolve an edge hit element to a qe-edge-* detail, or null. */
|
|
4509
|
+
_edgeDetail(el) {
|
|
4510
|
+
const e = this._closestWith(el, "data-edge-source");
|
|
4511
|
+
if (!e) return null;
|
|
4512
|
+
const d = {
|
|
4513
|
+
source: e.getAttribute("data-edge-source"),
|
|
4514
|
+
target: e.getAttribute("data-edge-target"),
|
|
4515
|
+
weight: parseFloat(e.getAttribute("data-edge-weight") || "0"),
|
|
4516
|
+
directed: e.getAttribute("data-edge-directed") === "true",
|
|
4517
|
+
self: e.getAttribute("data-edge-self") === "true"
|
|
4518
|
+
};
|
|
4519
|
+
if (e.hasAttribute("data-edge-group")) d.group = e.getAttribute("data-edge-group");
|
|
4520
|
+
if (e.hasAttribute("data-edge-unit")) d.unit = e.getAttribute("data-edge-unit");
|
|
4521
|
+
return d;
|
|
4522
|
+
}
|
|
4523
|
+
/** Resolve a node/mean/point hit element to a qe-node-* detail, or null. */
|
|
4524
|
+
_nodeDetail(el) {
|
|
4525
|
+
const n = this._closestWith(el, "data-point-id");
|
|
4526
|
+
if (!n) return null;
|
|
4527
|
+
const tag = n.tagName.toLowerCase();
|
|
4528
|
+
let x, y;
|
|
4529
|
+
if (tag === "rect") {
|
|
4530
|
+
x = parseFloat(n.getAttribute("x") || "0") + parseFloat(n.getAttribute("width") || "0") / 2;
|
|
4531
|
+
y = parseFloat(n.getAttribute("y") || "0") + parseFloat(n.getAttribute("height") || "0") / 2;
|
|
4532
|
+
} else {
|
|
4533
|
+
x = parseFloat(n.getAttribute("cx") || "0");
|
|
4534
|
+
y = parseFloat(n.getAttribute("cy") || "0");
|
|
4535
|
+
}
|
|
4536
|
+
const id = n.getAttribute("data-point-id");
|
|
4537
|
+
const d = {
|
|
4538
|
+
id,
|
|
4539
|
+
kind: n.getAttribute("data-kind") || "point",
|
|
4540
|
+
name: n.getAttribute("data-name") || id,
|
|
4541
|
+
x,
|
|
4542
|
+
y
|
|
4543
|
+
};
|
|
4544
|
+
if (n.hasAttribute("data-group")) d.group = n.getAttribute("data-group");
|
|
4545
|
+
if (n.hasAttribute("data-unit")) d.unit = n.getAttribute("data-unit");
|
|
4546
|
+
return d;
|
|
4547
|
+
}
|
|
4548
|
+
_attachInteractionEvents() {
|
|
4549
|
+
const svg = this.svg_element;
|
|
4550
|
+
svg.addEventListener("click", (ev) => {
|
|
4551
|
+
const target = ev.target;
|
|
4552
|
+
const edge = this._edgeDetail(target);
|
|
4553
|
+
if (edge) {
|
|
4554
|
+
this._emit("qe-edge-click", { ...edge, originalEvent: ev });
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
const node = this._nodeDetail(target);
|
|
4558
|
+
if (node) this._emit("qe-node-click", { ...node, originalEvent: ev });
|
|
4559
|
+
});
|
|
4560
|
+
let hovered = null;
|
|
4561
|
+
const keyOf = /* @__PURE__ */ __name((t, d) => t === "edge" ? `${d.source}\0${d.target}` : String(d.id), "keyOf");
|
|
4562
|
+
svg.addEventListener("mousemove", (ev) => {
|
|
4563
|
+
const target = ev.target;
|
|
4564
|
+
const edge = this._edgeDetail(target);
|
|
4565
|
+
const node = edge ? null : this._nodeDetail(target);
|
|
4566
|
+
const next = edge ? { type: "edge", detail: edge } : node ? { type: "node", detail: node } : null;
|
|
4567
|
+
const nextKey = next ? keyOf(next.type, next.detail) : null;
|
|
4568
|
+
if (hovered && (!next || hovered.key !== nextKey)) {
|
|
4569
|
+
this._emit(hovered.type === "edge" ? "qe-edge-unhover" : "qe-node-unhover", { key: hovered.key });
|
|
4570
|
+
hovered = null;
|
|
4571
|
+
}
|
|
4572
|
+
if (next && (!hovered || hovered.key !== nextKey)) {
|
|
4573
|
+
this._emit(next.type === "edge" ? "qe-edge-hover" : "qe-node-hover", { ...next.detail, originalEvent: ev });
|
|
4574
|
+
hovered = { type: next.type, key: nextKey };
|
|
4575
|
+
}
|
|
4576
|
+
if (this.hasAttribute("tooltip") && next) {
|
|
4577
|
+
this._showTooltip(this._tooltipText(next.type, next.detail), ev.clientX, ev.clientY);
|
|
4578
|
+
} else {
|
|
4579
|
+
this._hideTooltip();
|
|
4580
|
+
}
|
|
4581
|
+
});
|
|
4582
|
+
svg.addEventListener("mouseleave", () => {
|
|
4583
|
+
if (hovered) {
|
|
4584
|
+
this._emit(hovered.type === "edge" ? "qe-edge-unhover" : "qe-node-unhover", { key: hovered.key });
|
|
4585
|
+
hovered = null;
|
|
4586
|
+
}
|
|
4587
|
+
this._hideTooltip();
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4590
|
+
/** "source – target (weight)" for edges; the mark name for nodes/means/points. */
|
|
4591
|
+
_tooltipText(type, d) {
|
|
4592
|
+
if (type === "edge") {
|
|
4593
|
+
const w = Number(d.weight);
|
|
4594
|
+
const wStr = Number.isFinite(w) ? ` (${+w.toFixed(3)})` : "";
|
|
4595
|
+
return `${d.source} – ${d.target}${wStr}`;
|
|
4596
|
+
}
|
|
4597
|
+
return String(d.name ?? d.id);
|
|
4598
|
+
}
|
|
4599
|
+
_showTooltip(text, clientX, clientY) {
|
|
4600
|
+
if (!this._tooltipEl) {
|
|
4601
|
+
const el = document.createElement("div");
|
|
4602
|
+
el.setAttribute("data-qe-tooltip", "");
|
|
4603
|
+
Object.assign(el.style, {
|
|
4604
|
+
position: "fixed",
|
|
4605
|
+
zIndex: "2147483647",
|
|
4606
|
+
pointerEvents: "none",
|
|
4607
|
+
background: "rgba(20,20,20,0.92)",
|
|
4608
|
+
color: "#fff",
|
|
4609
|
+
font: "12px/1.4 system-ui, sans-serif",
|
|
4610
|
+
padding: "3px 7px",
|
|
4611
|
+
borderRadius: "4px",
|
|
4612
|
+
whiteSpace: "nowrap",
|
|
4613
|
+
transform: "translate(-50%, calc(-100% - 8px))"
|
|
4614
|
+
});
|
|
4615
|
+
document.body.appendChild(el);
|
|
4616
|
+
this._tooltipEl = el;
|
|
4617
|
+
}
|
|
4618
|
+
this._tooltipEl.textContent = text;
|
|
4619
|
+
this._tooltipEl.style.left = `${clientX}px`;
|
|
4620
|
+
this._tooltipEl.style.top = `${clientY}px`;
|
|
4621
|
+
this._tooltipEl.style.display = "block";
|
|
4622
|
+
}
|
|
4623
|
+
_hideTooltip() {
|
|
4624
|
+
if (this._tooltipEl) this._tooltipEl.style.display = "none";
|
|
3949
4625
|
}
|
|
3950
4626
|
draw() {
|
|
3951
4627
|
var _a;
|
|
@@ -3964,6 +4640,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3964
4640
|
this.update_labels();
|
|
3965
4641
|
this.update_extremes();
|
|
3966
4642
|
this.update_axes();
|
|
4643
|
+
this._applyHighlights();
|
|
3967
4644
|
}
|
|
3968
4645
|
/**
|
|
3969
4646
|
* Axes (<qe-axes>): toggle the axis-line cross by presence, and draw/position
|
|
@@ -3971,6 +4648,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
3971
4648
|
* cached SVG <text> nodes in the labels group; null text removes them.
|
|
3972
4649
|
*/
|
|
3973
4650
|
update_axes() {
|
|
4651
|
+
var _a;
|
|
3974
4652
|
const axesLayer = this.__children__.search("__children__", "axes");
|
|
3975
4653
|
if (axesLayer == null ? void 0 : axesLayer.svg_element) {
|
|
3976
4654
|
axesLayer.svg_element.style.display = this._showAxes ? "" : "none";
|
|
@@ -4002,17 +4680,37 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
4002
4680
|
el.setAttribute("font-weight", "600");
|
|
4003
4681
|
el.setAttribute("fill", "#556");
|
|
4004
4682
|
if (axis === "x") {
|
|
4005
|
-
el.setAttribute("x", (extent
|
|
4006
|
-
el.setAttribute("y", (
|
|
4007
|
-
el.setAttribute("text-anchor", "
|
|
4683
|
+
el.setAttribute("x", (extent - gap).toString());
|
|
4684
|
+
el.setAttribute("y", (-gap).toString());
|
|
4685
|
+
el.setAttribute("text-anchor", "end");
|
|
4008
4686
|
} else {
|
|
4009
|
-
el.setAttribute("x",
|
|
4010
|
-
el.setAttribute("y", (-extent
|
|
4011
|
-
el.setAttribute("text-anchor", "
|
|
4687
|
+
el.setAttribute("x", gap.toString());
|
|
4688
|
+
el.setAttribute("y", (-extent + fontSize + gap).toString());
|
|
4689
|
+
el.setAttribute("text-anchor", "start");
|
|
4012
4690
|
}
|
|
4013
4691
|
}, "place");
|
|
4014
4692
|
place("x", this._axisXText);
|
|
4015
4693
|
place("y", this._axisYText);
|
|
4694
|
+
if (this._magnifyText == null) {
|
|
4695
|
+
(_a = this._magnifyEl) == null ? void 0 : _a.remove();
|
|
4696
|
+
this._magnifyEl = null;
|
|
4697
|
+
} else {
|
|
4698
|
+
if (!this._magnifyEl) {
|
|
4699
|
+
this._magnifyEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
4700
|
+
this._magnifyEl.setAttribute("data-magnify-label", "");
|
|
4701
|
+
this._magnifyEl.setAttribute("pointer-events", "none");
|
|
4702
|
+
}
|
|
4703
|
+
if (!this._magnifyEl.parentElement) parent.appendChild(this._magnifyEl);
|
|
4704
|
+
const el = this._magnifyEl;
|
|
4705
|
+
el.textContent = this._magnifyText;
|
|
4706
|
+
el.setAttribute("font-size", (fontSize * 0.85).toString());
|
|
4707
|
+
el.setAttribute("font-family", "system-ui, sans-serif");
|
|
4708
|
+
el.setAttribute("font-style", "italic");
|
|
4709
|
+
el.setAttribute("fill", "#556");
|
|
4710
|
+
el.setAttribute("x", (-extent + gap).toString());
|
|
4711
|
+
el.setAttribute("y", (-extent + fontSize + gap).toString());
|
|
4712
|
+
el.setAttribute("text-anchor", "start");
|
|
4713
|
+
}
|
|
4016
4714
|
}
|
|
4017
4715
|
update_labels() {
|
|
4018
4716
|
this._nodeLabels.forEach((lbl) => lbl.update());
|
|
@@ -4076,8 +4774,12 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
4076
4774
|
background-color: #FFF;
|
|
4077
4775
|
}
|
|
4078
4776
|
`;
|
|
4777
|
+
_QEGraphElement._instances = /* @__PURE__ */ new Set();
|
|
4778
|
+
_QEGraphElement._PATH_NUM_RE = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
|
|
4079
4779
|
let QEGraphElement = _QEGraphElement;
|
|
4080
4780
|
customElements.define(QEGraphElement.tagName, QEGraphElement);
|
|
4781
|
+
const getGraph = /* @__PURE__ */ __name((key) => QEGraphElement.getGraph(key), "getGraph");
|
|
4782
|
+
const getGraphs = /* @__PURE__ */ __name(() => QEGraphElement.instances(), "getGraphs");
|
|
4081
4783
|
exports2.Color = Color;
|
|
4082
4784
|
exports2.DataFrame = DataFrame;
|
|
4083
4785
|
exports2.DataFrameColumn = DataFrameColumn;
|
|
@@ -4101,6 +4803,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
|
|
|
4101
4803
|
exports2.Trajectory = Trajectory;
|
|
4102
4804
|
exports2.WeightedColor = WeightedColor;
|
|
4103
4805
|
exports2.fitTrajectory = fitTrajectory;
|
|
4806
|
+
exports2.getGraph = getGraph;
|
|
4807
|
+
exports2.getGraphs = getGraphs;
|
|
4104
4808
|
exports2.nearOriginFilter = nearOriginFilter;
|
|
4105
4809
|
exports2.sampleCurve = sampleCurve;
|
|
4106
4810
|
exports2.selectDegree = selectDegree;
|