@qe-libs/qeviz 0.1.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 +124 -1
- package/dist/qeviz.es.js +1381 -78
- package/dist/qeviz.es.js.map +1 -1
- package/dist/qeviz.umd.js +1381 -78
- package/dist/qeviz.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/qeviz.es.js
CHANGED
|
@@ -160,7 +160,7 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
__privateSet(this, _names2, names);
|
|
163
|
-
if (rowNames && rowNames.length !== this[0].length) {
|
|
163
|
+
if (rowNames && this[0] && rowNames.length !== this[0].length) {
|
|
164
164
|
throw new Error("Row names length must match the number of rows in the DataFrame.");
|
|
165
165
|
}
|
|
166
166
|
if (rowNames && rowNames.length > 0) {
|
|
@@ -224,6 +224,9 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
224
224
|
rows(...wh) {
|
|
225
225
|
let selectors, selectedRows = [], selectedRowNames = [];
|
|
226
226
|
if (wh.length === 0) {
|
|
227
|
+
if (!this[0] || this.length === 0) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
227
230
|
const numRows = this[0].length;
|
|
228
231
|
selectedRows = Array.from({ length: numRows }, (_, i) => this.getRowByIndex(i));
|
|
229
232
|
selectedRowNames = __privateGet(this, _rowNames2);
|
|
@@ -282,7 +285,7 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
282
285
|
return __privateGet(this, _rowNames2);
|
|
283
286
|
}
|
|
284
287
|
get nrow() {
|
|
285
|
-
return this[0].length;
|
|
288
|
+
return this[0] ? this[0].length : 0;
|
|
286
289
|
}
|
|
287
290
|
get ncol() {
|
|
288
291
|
return this.length;
|
|
@@ -349,6 +352,12 @@ const _Model = class _Model {
|
|
|
349
352
|
set groups(v) {
|
|
350
353
|
this._groups = v;
|
|
351
354
|
}
|
|
355
|
+
get trajectories() {
|
|
356
|
+
return this._trajectories;
|
|
357
|
+
}
|
|
358
|
+
set trajectories(v) {
|
|
359
|
+
this._trajectories = v;
|
|
360
|
+
}
|
|
352
361
|
get dimensions() {
|
|
353
362
|
return this._dimensions;
|
|
354
363
|
}
|
|
@@ -461,7 +470,7 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
461
470
|
*/
|
|
462
471
|
setModelData(data) {
|
|
463
472
|
var _a, _b, _c, _d;
|
|
464
|
-
const frameKeys = ["nodes", "edges", "points", "groups", "confidence", "outlier"];
|
|
473
|
+
const frameKeys = ["nodes", "edges", "points", "groups", "confidence", "outlier", "trajectories"];
|
|
465
474
|
frameKeys.forEach((name) => {
|
|
466
475
|
var _a2;
|
|
467
476
|
const raw = data[name];
|
|
@@ -470,7 +479,7 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
470
479
|
let idCol;
|
|
471
480
|
if (name === "nodes") {
|
|
472
481
|
idCol = data.node_id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
473
|
-
} else if (name === "edges" || name === "points") {
|
|
482
|
+
} else if (name === "edges" || name === "points" || name === "trajectories") {
|
|
474
483
|
idCol = data.id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
475
484
|
} else {
|
|
476
485
|
idCol = data.group_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
@@ -540,8 +549,12 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
540
549
|
__name(_QEVisualElement, "QEVisualElement");
|
|
541
550
|
_QEVisualElement.tagName = "qe-visual";
|
|
542
551
|
_QEVisualElement.styles = `
|
|
543
|
-
:host { display:
|
|
552
|
+
:host { display: block; width: 100%; }
|
|
544
553
|
:host(.hidden) { display: none !important; }
|
|
554
|
+
/* The output wrapper and the slotted graph fill the host so the plot
|
|
555
|
+
stretches to its container instead of collapsing to intrinsic size. */
|
|
556
|
+
div { display: block; width: 100%; height: 100%; }
|
|
557
|
+
::slotted(qe-graph) { display: block; width: 100%; height: 100%; }
|
|
545
558
|
`;
|
|
546
559
|
let QEVisualElement = _QEVisualElement;
|
|
547
560
|
customElements.define(QEVisualElement.tagName, QEVisualElement);
|
|
@@ -582,9 +595,10 @@ const _Color = class _Color {
|
|
|
582
595
|
__name(_Color, "Color");
|
|
583
596
|
let Color = _Color;
|
|
584
597
|
const _WeightedColor = class _WeightedColor extends Color {
|
|
585
|
-
constructor(base, weight) {
|
|
598
|
+
constructor(base, weight, alpha) {
|
|
586
599
|
super(base);
|
|
587
600
|
this.weight = weight;
|
|
601
|
+
this.alpha = alpha;
|
|
588
602
|
}
|
|
589
603
|
// Parse hex → HSV (h in [0, 360), s/v in [0, 1]).
|
|
590
604
|
static toHSV(hex) {
|
|
@@ -655,7 +669,8 @@ const _WeightedColor = class _WeightedColor extends Color {
|
|
|
655
669
|
toString() {
|
|
656
670
|
const { h, v } = _WeightedColor.toHSV(this.base);
|
|
657
671
|
const w = Math.min(1, Math.abs(this.weight));
|
|
658
|
-
|
|
672
|
+
const a = this.alpha === void 0 ? w : Math.min(1, Math.max(0, this.alpha));
|
|
673
|
+
return _WeightedColor.hsvToRgba(h, w, v, a);
|
|
659
674
|
}
|
|
660
675
|
};
|
|
661
676
|
__name(_WeightedColor, "WeightedColor");
|
|
@@ -812,7 +827,9 @@ var Tools;
|
|
|
812
827
|
__name(slopeIntercept, "slopeIntercept");
|
|
813
828
|
Line2.slopeIntercept = slopeIntercept;
|
|
814
829
|
function point_along(line, distance = 0) {
|
|
815
|
-
|
|
830
|
+
const ratio = distance;
|
|
831
|
+
const Xt = (1 - ratio) * line[0][0] + ratio * line[1][0];
|
|
832
|
+
const Yt = (1 - ratio) * line[0][1] + ratio * line[1][1];
|
|
816
833
|
return [Xt, Yt];
|
|
817
834
|
}
|
|
818
835
|
__name(point_along, "point_along");
|
|
@@ -1659,6 +1676,7 @@ const _Graph = class _Graph {
|
|
|
1659
1676
|
this.points = /* @__PURE__ */ new Map();
|
|
1660
1677
|
this.nodes = /* @__PURE__ */ new Map();
|
|
1661
1678
|
this.edges = /* @__PURE__ */ new Map();
|
|
1679
|
+
this.trajectories = /* @__PURE__ */ new Map();
|
|
1662
1680
|
this.labels = /* @__PURE__ */ new Map();
|
|
1663
1681
|
this.transform = new Transform();
|
|
1664
1682
|
this.scale_x = 1;
|
|
@@ -1701,6 +1719,7 @@ const _Graph = class _Graph {
|
|
|
1701
1719
|
{ name: "edgenodes", layers: [], tabindex: 0 }
|
|
1702
1720
|
]
|
|
1703
1721
|
},
|
|
1722
|
+
{ name: "trajectories", layers: [], tabindex: 0 },
|
|
1704
1723
|
{ name: "points", layers: [], tabindex: 0 },
|
|
1705
1724
|
{ name: "labels", layers: [], tabindex: 0 }
|
|
1706
1725
|
] }
|
|
@@ -1826,9 +1845,13 @@ const _Graph = class _Graph {
|
|
|
1826
1845
|
if (search_layers.length === 0) {
|
|
1827
1846
|
this.extremes = new Extremes(1, 1);
|
|
1828
1847
|
} else {
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1848
|
+
const content_max = Math.max(
|
|
1849
|
+
...search_layers.map((l) => Math.max(
|
|
1850
|
+
...[...l.objects.values()].map((o) => Math.max(Math.abs(o.x), Math.abs(o.y)))
|
|
1851
|
+
)).filter((a) => Math.abs(a) !== Infinity)
|
|
1852
|
+
);
|
|
1853
|
+
const found_max = (Number.isFinite(content_max) && content_max > 0 ? content_max : 1) * 1.3;
|
|
1854
|
+
this.extremes = new Extremes(found_max, found_max);
|
|
1832
1855
|
}
|
|
1833
1856
|
}
|
|
1834
1857
|
base_point_size(scale = 8) {
|
|
@@ -1868,6 +1891,251 @@ _DrawableElement.style = `
|
|
|
1868
1891
|
}
|
|
1869
1892
|
`;
|
|
1870
1893
|
let DrawableElement = _DrawableElement;
|
|
1894
|
+
const TRAJECTORY_ZERO_EPS = 1e-8;
|
|
1895
|
+
function nearOriginFilter(pts, eps = TRAJECTORY_ZERO_EPS) {
|
|
1896
|
+
return pts.filter((p) => Math.abs(p.x) > eps || Math.abs(p.y) > eps);
|
|
1897
|
+
}
|
|
1898
|
+
__name(nearOriginFilter, "nearOriginFilter");
|
|
1899
|
+
function normalizedT(n) {
|
|
1900
|
+
if (n <= 0) return [];
|
|
1901
|
+
if (n === 1) return [0];
|
|
1902
|
+
const t = new Array(n);
|
|
1903
|
+
for (let i = 0; i < n; i++) t[i] = i / (n - 1);
|
|
1904
|
+
return t;
|
|
1905
|
+
}
|
|
1906
|
+
__name(normalizedT, "normalizedT");
|
|
1907
|
+
function design(t, degree) {
|
|
1908
|
+
return t.map((ti) => {
|
|
1909
|
+
const row = new Array(degree + 1);
|
|
1910
|
+
let p = 1;
|
|
1911
|
+
for (let k = 0; k <= degree; k++) {
|
|
1912
|
+
row[k] = p;
|
|
1913
|
+
p *= ti;
|
|
1914
|
+
}
|
|
1915
|
+
return row;
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
__name(design, "design");
|
|
1919
|
+
function qr(A) {
|
|
1920
|
+
const n = A.length;
|
|
1921
|
+
const m = A[0].length;
|
|
1922
|
+
const Q = A.map((row) => row.slice());
|
|
1923
|
+
const R = Array.from({ length: m }, () => new Array(m).fill(0));
|
|
1924
|
+
for (let j = 0; j < m; j++) {
|
|
1925
|
+
let norm = 0;
|
|
1926
|
+
for (let i = 0; i < n; i++) norm += Q[i][j] * Q[i][j];
|
|
1927
|
+
norm = Math.sqrt(norm);
|
|
1928
|
+
R[j][j] = norm;
|
|
1929
|
+
if (norm > 1e-300) {
|
|
1930
|
+
for (let i = 0; i < n; i++) Q[i][j] /= norm;
|
|
1931
|
+
}
|
|
1932
|
+
for (let k = j + 1; k < m; k++) {
|
|
1933
|
+
let dot = 0;
|
|
1934
|
+
for (let i = 0; i < n; i++) dot += Q[i][j] * Q[i][k];
|
|
1935
|
+
R[j][k] = dot;
|
|
1936
|
+
for (let i = 0; i < n; i++) Q[i][k] -= dot * Q[i][j];
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
return { Q, R };
|
|
1940
|
+
}
|
|
1941
|
+
__name(qr, "qr");
|
|
1942
|
+
function qtY(Q, y) {
|
|
1943
|
+
const n = Q.length;
|
|
1944
|
+
const m = Q[0].length;
|
|
1945
|
+
const out = new Array(m).fill(0);
|
|
1946
|
+
for (let j = 0; j < m; j++) {
|
|
1947
|
+
let s = 0;
|
|
1948
|
+
for (let i = 0; i < n; i++) s += Q[i][j] * y[i];
|
|
1949
|
+
out[j] = s;
|
|
1950
|
+
}
|
|
1951
|
+
return out;
|
|
1952
|
+
}
|
|
1953
|
+
__name(qtY, "qtY");
|
|
1954
|
+
function backSolve(R, b) {
|
|
1955
|
+
const m = b.length;
|
|
1956
|
+
const c = new Array(m).fill(0);
|
|
1957
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
1958
|
+
let s = b[i];
|
|
1959
|
+
for (let j = i + 1; j < m; j++) s -= R[i][j] * c[j];
|
|
1960
|
+
c[i] = Math.abs(R[i][i]) > 1e-12 ? s / R[i][i] : 0;
|
|
1961
|
+
}
|
|
1962
|
+
return c;
|
|
1963
|
+
}
|
|
1964
|
+
__name(backSolve, "backSolve");
|
|
1965
|
+
function leverages(Q) {
|
|
1966
|
+
return Q.map((row) => {
|
|
1967
|
+
let s = 0;
|
|
1968
|
+
for (let j = 0; j < row.length; j++) s += row[j] * row[j];
|
|
1969
|
+
return s;
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
__name(leverages, "leverages");
|
|
1973
|
+
function fitCoeffs(t, y, degree) {
|
|
1974
|
+
const X = design(t, degree);
|
|
1975
|
+
const { Q, R } = qr(X);
|
|
1976
|
+
return backSolve(R, qtY(Q, y));
|
|
1977
|
+
}
|
|
1978
|
+
__name(fitCoeffs, "fitCoeffs");
|
|
1979
|
+
function evalPoly(coeffs, t) {
|
|
1980
|
+
let v = 0;
|
|
1981
|
+
let p = 1;
|
|
1982
|
+
for (let k = 0; k < coeffs.length; k++) {
|
|
1983
|
+
v += coeffs[k] * p;
|
|
1984
|
+
p *= t;
|
|
1985
|
+
}
|
|
1986
|
+
return v;
|
|
1987
|
+
}
|
|
1988
|
+
__name(evalPoly, "evalPoly");
|
|
1989
|
+
function loocv2d(t, x, y, degree) {
|
|
1990
|
+
const X = design(t, degree);
|
|
1991
|
+
const { Q, R } = qr(X);
|
|
1992
|
+
const h = leverages(Q);
|
|
1993
|
+
const cx = backSolve(R, qtY(Q, x));
|
|
1994
|
+
const cy = backSolve(R, qtY(Q, y));
|
|
1995
|
+
let press = 0;
|
|
1996
|
+
for (let i = 0; i < t.length; i++) {
|
|
1997
|
+
const denom = 1 - h[i];
|
|
1998
|
+
if (denom <= 0) return Infinity;
|
|
1999
|
+
const ex = x[i] - evalPoly(cx, t[i]);
|
|
2000
|
+
const ey = y[i] - evalPoly(cy, t[i]);
|
|
2001
|
+
press += (ex / denom) ** 2 + (ey / denom) ** 2;
|
|
2002
|
+
}
|
|
2003
|
+
return press;
|
|
2004
|
+
}
|
|
2005
|
+
__name(loocv2d, "loocv2d");
|
|
2006
|
+
function selectDegree(fitPts, maxDegree, mode) {
|
|
2007
|
+
const n = fitPts.length;
|
|
2008
|
+
const cap = Math.min(Math.max(1, Math.floor(maxDegree)), Math.max(1, n - 1));
|
|
2009
|
+
if (mode === "fixed" || n < 3) return cap;
|
|
2010
|
+
const t = normalizedT(n);
|
|
2011
|
+
const x = fitPts.map((p) => p.x);
|
|
2012
|
+
const y = fitPts.map((p) => p.y);
|
|
2013
|
+
let best = 1;
|
|
2014
|
+
let bestVal = Infinity;
|
|
2015
|
+
for (let deg = 1; deg <= cap; deg++) {
|
|
2016
|
+
const v = loocv2d(t, x, y, deg);
|
|
2017
|
+
if (v < bestVal) {
|
|
2018
|
+
bestVal = v;
|
|
2019
|
+
best = deg;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return best;
|
|
2023
|
+
}
|
|
2024
|
+
__name(selectDegree, "selectDegree");
|
|
2025
|
+
function fitTrajectory(activePts, opts) {
|
|
2026
|
+
const fitPts = nearOriginFilter(activePts);
|
|
2027
|
+
if (fitPts.length < 3) return null;
|
|
2028
|
+
const selBasis = opts.selectionSet ? nearOriginFilter(opts.selectionSet) : fitPts;
|
|
2029
|
+
const basis = selBasis.length >= 3 ? selBasis : fitPts;
|
|
2030
|
+
let degree = selectDegree(basis, opts.maxDegree, opts.degreeSelection);
|
|
2031
|
+
degree = Math.min(degree, Math.max(1, fitPts.length - 1));
|
|
2032
|
+
const t = normalizedT(fitPts.length);
|
|
2033
|
+
const coeffsX = fitCoeffs(t, fitPts.map((p) => p.x), degree);
|
|
2034
|
+
const coeffsY = fitCoeffs(t, fitPts.map((p) => p.y), degree);
|
|
2035
|
+
return { degree, coeffsX, coeffsY };
|
|
2036
|
+
}
|
|
2037
|
+
__name(fitTrajectory, "fitTrajectory");
|
|
2038
|
+
function sampleCurve(fit, steps = 100) {
|
|
2039
|
+
const out = [];
|
|
2040
|
+
for (let s = 0; s <= steps; s++) {
|
|
2041
|
+
const t = s / steps;
|
|
2042
|
+
out.push({ x: evalPoly(fit.coeffsX, t), y: evalPoly(fit.coeffsY, t) });
|
|
2043
|
+
}
|
|
2044
|
+
return out;
|
|
2045
|
+
}
|
|
2046
|
+
__name(sampleCurve, "sampleCurve");
|
|
2047
|
+
const TRAJECTORY_CURVE_STEPS = 100;
|
|
2048
|
+
const _Trajectory = class _Trajectory extends PlottedObject {
|
|
2049
|
+
constructor(options = {}) {
|
|
2050
|
+
const defaultColor = options.color instanceof Color ? options.color : new Color(typeof options.color === "string" ? options.color : "#000000");
|
|
2051
|
+
super([], {
|
|
2052
|
+
ID: options.ID || `traj-${Math.random().toString(36).substring(2, 9)}`,
|
|
2053
|
+
size: options.size || 1,
|
|
2054
|
+
color: defaultColor,
|
|
2055
|
+
highlight: null,
|
|
2056
|
+
active: options.active || false,
|
|
2057
|
+
meta: options.meta || {},
|
|
2058
|
+
...options
|
|
2059
|
+
});
|
|
2060
|
+
this._typeName = "Trajectory";
|
|
2061
|
+
this.trajPoints = [];
|
|
2062
|
+
this.smooth = "none";
|
|
2063
|
+
this.maxDegree = 3;
|
|
2064
|
+
this.degreeSelection = "loocv_2d";
|
|
2065
|
+
this.selectedDegree = -1;
|
|
2066
|
+
this.polyCoeffsX = [];
|
|
2067
|
+
this.polyCoeffsY = [];
|
|
2068
|
+
this.currentTime = Infinity;
|
|
2069
|
+
this.showPoints = true;
|
|
2070
|
+
this.showTips = true;
|
|
2071
|
+
this.lineStyle = "uniform";
|
|
2072
|
+
this.strokeWidth = 2;
|
|
2073
|
+
this.strokeWidthRange = [1, 6];
|
|
2074
|
+
this.opacityRange = [0.3, 1];
|
|
2075
|
+
this.smooth = options.smooth || "none";
|
|
2076
|
+
this.maxDegree = options.maxDegree !== void 0 ? options.maxDegree : 3;
|
|
2077
|
+
this.degreeSelection = options.degreeSelection || "loocv_2d";
|
|
2078
|
+
this.polyCoeffsX = options.polyCoeffsX || [];
|
|
2079
|
+
this.polyCoeffsY = options.polyCoeffsY || [];
|
|
2080
|
+
this.currentTime = options.currentTime !== void 0 ? options.currentTime : Infinity;
|
|
2081
|
+
this.showPoints = options.showPoints !== void 0 ? options.showPoints : true;
|
|
2082
|
+
this.showTips = options.showTips !== void 0 ? options.showTips : true;
|
|
2083
|
+
this.lineStyle = options.lineStyle || "uniform";
|
|
2084
|
+
this.strokeWidth = options.strokeWidth || 2;
|
|
2085
|
+
this.strokeWidthRange = options.strokeWidthRange || [1, 6];
|
|
2086
|
+
this.opacityRange = options.opacityRange || [0.3, 1];
|
|
2087
|
+
if (options.points && Array.isArray(options.points)) {
|
|
2088
|
+
this.trajPoints = options.points.map((p) => {
|
|
2089
|
+
if (p instanceof Point) return p;
|
|
2090
|
+
const pt = new Point({ dimensions: [p.x, p.y], meta: { time: p.time, label: p.label } });
|
|
2091
|
+
pt.x = p.x;
|
|
2092
|
+
pt.y = p.y;
|
|
2093
|
+
return pt;
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
get activePoints() {
|
|
2098
|
+
if (this.currentTime === Infinity) {
|
|
2099
|
+
return this.trajPoints;
|
|
2100
|
+
}
|
|
2101
|
+
return this.trajPoints.filter((p) => {
|
|
2102
|
+
const t = p.meta && typeof p.meta.time === "number" ? p.meta.time : 0;
|
|
2103
|
+
return t <= this.currentTime;
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
get path() {
|
|
2107
|
+
return this.pathPoints.reduce((acc, pt, idx) => {
|
|
2108
|
+
return idx === 0 ? `M${pt.x},${pt.y}` : `${acc} L${pt.x},${pt.y}`;
|
|
2109
|
+
}, "");
|
|
2110
|
+
}
|
|
2111
|
+
get pathPoints() {
|
|
2112
|
+
const pts = this.activePoints;
|
|
2113
|
+
if (pts.length < 2) return [];
|
|
2114
|
+
if (this.smooth === "poly") {
|
|
2115
|
+
if (this.polyCoeffsX.length > 0 && this.polyCoeffsY.length > 0) {
|
|
2116
|
+
return sampleCurve(
|
|
2117
|
+
{ degree: this.polyCoeffsX.length - 1, coeffsX: this.polyCoeffsX, coeffsY: this.polyCoeffsY },
|
|
2118
|
+
TRAJECTORY_CURVE_STEPS
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
const fit = fitTrajectory(
|
|
2122
|
+
pts.map((p) => ({ x: p.x, y: p.y })),
|
|
2123
|
+
{
|
|
2124
|
+
maxDegree: this.maxDegree,
|
|
2125
|
+
degreeSelection: this.degreeSelection,
|
|
2126
|
+
selectionSet: this.trajPoints.map((p) => ({ x: p.x, y: p.y }))
|
|
2127
|
+
}
|
|
2128
|
+
);
|
|
2129
|
+
if (fit) {
|
|
2130
|
+
this.selectedDegree = fit.degree;
|
|
2131
|
+
return sampleCurve(fit, TRAJECTORY_CURVE_STEPS);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return pts.map((pt) => ({ x: pt.x, y: pt.y }));
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
__name(_Trajectory, "Trajectory");
|
|
2138
|
+
let Trajectory = _Trajectory;
|
|
1871
2139
|
const zoom_level = 1;
|
|
1872
2140
|
const _Edge = class _Edge extends Line {
|
|
1873
2141
|
constructor(data, from, to, weight = 0) {
|
|
@@ -1914,9 +2182,22 @@ const _EdgeArrow = class _EdgeArrow extends PlottedObject {
|
|
|
1914
2182
|
return __privateGet(this, _edge);
|
|
1915
2183
|
}
|
|
1916
2184
|
get path() {
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2185
|
+
const _this = __privateGet(this, _edge);
|
|
2186
|
+
const T = [_this.to.x, _this.to.y];
|
|
2187
|
+
const A = _this.from_point;
|
|
2188
|
+
const [B0, B1] = _this.base_pts;
|
|
2189
|
+
const L = Math.hypot(A[0] - T[0], A[1] - T[1]);
|
|
2190
|
+
const W = Math.hypot(B0[0] - B1[0], B0[1] - B1[1]);
|
|
2191
|
+
if (!(L > 0) || !(W > 0)) return "";
|
|
2192
|
+
const u = [(A[0] - T[0]) / L, (A[1] - T[1]) / L];
|
|
2193
|
+
const b = Math.hypot(B0[0] - T[0], B0[1] - T[1]);
|
|
2194
|
+
const v = [(B0[0] - T[0]) / b, (B0[1] - T[1]) / b];
|
|
2195
|
+
const halfW = /* @__PURE__ */ __name((d) => W / 2 * Math.max(0, 1 - d / L), "halfW");
|
|
2196
|
+
const at = /* @__PURE__ */ __name((d, s) => `${T[0] + u[0] * d + v[0] * s} ${T[1] + u[1] * d + v[1] * s}`, "at");
|
|
2197
|
+
const dh = 0.2 * L;
|
|
2198
|
+
const c = Math.min(0.15 * L, 0.8 * 2 * halfW(dh));
|
|
2199
|
+
const dm = dh + c / 2, dn = dh + c;
|
|
2200
|
+
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`;
|
|
1920
2201
|
return p;
|
|
1921
2202
|
}
|
|
1922
2203
|
};
|
|
@@ -2059,6 +2340,46 @@ const _Drawable = class _Drawable {
|
|
|
2059
2340
|
};
|
|
2060
2341
|
__name(_Drawable, "Drawable");
|
|
2061
2342
|
let Drawable = _Drawable;
|
|
2343
|
+
const DEFAULT_EDGE_WIDTH = 0.04;
|
|
2344
|
+
const SATURATION_RANGE = [0.25, 1];
|
|
2345
|
+
const OPACITY_RANGE = [0.3, 1];
|
|
2346
|
+
function rampFor(w, lo, hi, range) {
|
|
2347
|
+
const a = Math.abs(w);
|
|
2348
|
+
if (!(a > 0)) return 0;
|
|
2349
|
+
if (!(hi > lo)) return range[1];
|
|
2350
|
+
const t = Math.min(1, Math.max(0, (a - lo) / (hi - lo)));
|
|
2351
|
+
return range[0] + t * (range[1] - range[0]);
|
|
2352
|
+
}
|
|
2353
|
+
__name(rampFor, "rampFor");
|
|
2354
|
+
const EDGE_WIDTH_PER_EXTENT = 0.011;
|
|
2355
|
+
const TAPERED_OUTLINE_PX = 0.75;
|
|
2356
|
+
const CHEVRON_DARKEN = 0.8;
|
|
2357
|
+
const CHEVRON_HALO_PX = 0.75;
|
|
2358
|
+
const POINT_RADIUS_PER_EXTENT = 6e-3;
|
|
2359
|
+
const UNCONNECTED_NODE_SIZE = 0.6;
|
|
2360
|
+
function widthPerWeight(edgeWidth) {
|
|
2361
|
+
return 2 * edgeWidth;
|
|
2362
|
+
}
|
|
2363
|
+
__name(widthPerWeight, "widthPerWeight");
|
|
2364
|
+
function nodeSizeForEdges(weightSum, widthFrac) {
|
|
2365
|
+
return weightSum * widthFrac / (2 * POINT_RADIUS_PER_EXTENT);
|
|
2366
|
+
}
|
|
2367
|
+
__name(nodeSizeForEdges, "nodeSizeForEdges");
|
|
2368
|
+
function flattenOnWhite(col) {
|
|
2369
|
+
const m = /rgba?\(([^)]+)\)/.exec(col);
|
|
2370
|
+
if (!m) return col;
|
|
2371
|
+
const [r, g, b, a = 1] = m[1].split(",").map(Number);
|
|
2372
|
+
const f = /* @__PURE__ */ __name((c) => Math.round(255 + (c - 255) * a), "f");
|
|
2373
|
+
return `rgb(${f(r)},${f(g)},${f(b)})`;
|
|
2374
|
+
}
|
|
2375
|
+
__name(flattenOnWhite, "flattenOnWhite");
|
|
2376
|
+
function darkenRgba(col, k) {
|
|
2377
|
+
const m = /rgba?\(([^)]+)\)/.exec(col);
|
|
2378
|
+
if (!m) return col;
|
|
2379
|
+
const [r, g, b, a = 1] = m[1].split(",").map(Number);
|
|
2380
|
+
return `rgba(${Math.round(r * k)},${Math.round(g * k)},${Math.round(b * k)},${a})`;
|
|
2381
|
+
}
|
|
2382
|
+
__name(darkenRgba, "darkenRgba");
|
|
2062
2383
|
const _QELine = class _QELine extends Drawable {
|
|
2063
2384
|
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2064
2385
|
super(__data__, parent, graph, __children__);
|
|
@@ -2068,8 +2389,22 @@ const _QELine = class _QELine extends Drawable {
|
|
|
2068
2389
|
this.__children__ = __children__;
|
|
2069
2390
|
this.__name__ = "unnamed-line";
|
|
2070
2391
|
this._arrow_el = null;
|
|
2392
|
+
this._hit_el = null;
|
|
2071
2393
|
this.svg_element = this.create();
|
|
2072
2394
|
}
|
|
2395
|
+
/** Stamp edge identity onto an element so <qe-graph> delegation can resolve
|
|
2396
|
+
* qe-edge-* events. No-op for non-edges (e.g. axis lines). */
|
|
2397
|
+
_stampEdge(el) {
|
|
2398
|
+
const meta = this.__data__.meta ?? {};
|
|
2399
|
+
if (!meta.isMeanEdge) return;
|
|
2400
|
+
el.setAttribute("data-edge-source", String(meta.source ?? ""));
|
|
2401
|
+
el.setAttribute("data-edge-target", String(meta.target ?? ""));
|
|
2402
|
+
el.setAttribute("data-edge-weight", String(meta.weight ?? 0));
|
|
2403
|
+
el.setAttribute("data-edge-directed", meta.isDirected ? "true" : "false");
|
|
2404
|
+
if (meta.isSelf) el.setAttribute("data-edge-self", "true");
|
|
2405
|
+
if (meta.group != null) el.setAttribute("data-edge-group", String(meta.group));
|
|
2406
|
+
if (meta.unit != null) el.setAttribute("data-edge-unit", String(meta.unit));
|
|
2407
|
+
}
|
|
2073
2408
|
create() {
|
|
2074
2409
|
if (this.__data__ instanceof NodeEdge) {
|
|
2075
2410
|
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
@@ -2106,24 +2441,28 @@ const _QELine = class _QELine extends Drawable {
|
|
|
2106
2441
|
const extent = ((_a = this.graph.__data__.extremes) == null ? void 0 : _a.x_max) ?? 1;
|
|
2107
2442
|
const meta = this.__data__.meta ?? {};
|
|
2108
2443
|
if (meta.isMeanEdge) {
|
|
2444
|
+
this._stampEdge(this.svg_element);
|
|
2109
2445
|
const weight = typeof meta.weight === "number" ? meta.weight : 0;
|
|
2110
2446
|
const maxW = typeof meta.maxWeight === "number" ? meta.maxWeight : 1;
|
|
2111
2447
|
const opacity = typeof meta.opacity === "number" ? meta.opacity : Math.min(1, weight / (maxW || 1));
|
|
2112
|
-
const
|
|
2448
|
+
const ext = extent > 0 ? extent : 1;
|
|
2449
|
+
const sw = typeof meta.widthFrac === "number" ? ext * meta.widthFrac * weight : ext * EDGE_WIDTH_PER_EXTENT * (weight / (maxW || 1));
|
|
2113
2450
|
const baseColor = this.__data__._color || new Color("#000000");
|
|
2114
|
-
const
|
|
2451
|
+
const saturation = typeof meta.saturation === "number" ? meta.saturation : opacity;
|
|
2452
|
+
const edgeColor = new WeightedColor(baseColor, saturation, opacity).toString();
|
|
2115
2453
|
if (this.__data__ instanceof NodeEdge) {
|
|
2116
|
-
|
|
2117
|
-
const nodeR = extent * 6e-3 * nodeSize;
|
|
2118
|
-
this.__data__.weight = 2 * nodeR * 0.55 * opacity;
|
|
2454
|
+
this.__data__.weight = sw;
|
|
2119
2455
|
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2120
2456
|
this.svg_element.setAttribute("fill", edgeColor);
|
|
2121
2457
|
this.svg_element.setAttribute("stroke", "none");
|
|
2122
2458
|
} else if (this.__data__ instanceof TaperedEdge) {
|
|
2123
|
-
this.__data__.weight = sw
|
|
2459
|
+
this.__data__.weight = sw;
|
|
2124
2460
|
this.svg_element.setAttribute("d", this.__data__.path);
|
|
2125
2461
|
this.svg_element.setAttribute("fill", edgeColor);
|
|
2126
|
-
this.svg_element.setAttribute("stroke",
|
|
2462
|
+
this.svg_element.setAttribute("stroke", edgeColor);
|
|
2463
|
+
this.svg_element.setAttribute("stroke-width", String(TAPERED_OUTLINE_PX));
|
|
2464
|
+
this.svg_element.setAttribute("vector-effect", "non-scaling-stroke");
|
|
2465
|
+
this.svg_element.setAttribute("stroke-linejoin", "round");
|
|
2127
2466
|
if (meta.isDirected) {
|
|
2128
2467
|
try {
|
|
2129
2468
|
const arrow = new EdgeArrow(this.__data__);
|
|
@@ -2137,20 +2476,39 @@ const _QELine = class _QELine extends Drawable {
|
|
|
2137
2476
|
this.parent.appendChild(this._arrow_el);
|
|
2138
2477
|
}
|
|
2139
2478
|
this._arrow_el.setAttribute("d", arrowPath);
|
|
2140
|
-
|
|
2141
|
-
this._arrow_el.setAttribute("
|
|
2479
|
+
this._arrow_el.setAttribute("fill", darkenRgba(flattenOnWhite(edgeColor), CHEVRON_DARKEN));
|
|
2480
|
+
this._arrow_el.setAttribute("stroke", "#ffffff");
|
|
2481
|
+
this._arrow_el.setAttribute("stroke-width", String(CHEVRON_HALO_PX));
|
|
2482
|
+
this._arrow_el.setAttribute("vector-effect", "non-scaling-stroke");
|
|
2483
|
+
this._arrow_el.setAttribute("stroke-linejoin", "round");
|
|
2142
2484
|
}
|
|
2143
2485
|
} catch (_) {
|
|
2144
2486
|
}
|
|
2145
2487
|
}
|
|
2146
2488
|
} else {
|
|
2147
|
-
this.
|
|
2148
|
-
this.
|
|
2149
|
-
this.svg_element.setAttribute("
|
|
2150
|
-
this.svg_element.setAttribute("
|
|
2489
|
+
const x1 = this.__data__.from.x, y1 = this.__data__.from.y;
|
|
2490
|
+
const x2 = this.__data__.to.x, y2 = this.__data__.to.y;
|
|
2491
|
+
this.svg_element.setAttribute("x1", x1.toString());
|
|
2492
|
+
this.svg_element.setAttribute("y1", y1.toString());
|
|
2493
|
+
this.svg_element.setAttribute("x2", x2.toString());
|
|
2494
|
+
this.svg_element.setAttribute("y2", y2.toString());
|
|
2151
2495
|
this.svg_element.setAttribute("stroke-width", sw.toString());
|
|
2152
2496
|
this.svg_element.setAttribute("stroke", edgeColor);
|
|
2497
|
+
this.svg_element.setAttribute("pointer-events", "stroke");
|
|
2153
2498
|
this.svg_element.removeAttribute("marker-end");
|
|
2499
|
+
if (!this._hit_el) {
|
|
2500
|
+
this._hit_el = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
2501
|
+
this._hit_el.setAttribute("stroke", "transparent");
|
|
2502
|
+
this._hit_el.setAttribute("stroke-linecap", "round");
|
|
2503
|
+
this._hit_el.setAttribute("pointer-events", "stroke");
|
|
2504
|
+
}
|
|
2505
|
+
if (!this._hit_el.parentElement) this.parent.appendChild(this._hit_el);
|
|
2506
|
+
this._hit_el.setAttribute("x1", x1.toString());
|
|
2507
|
+
this._hit_el.setAttribute("y1", y1.toString());
|
|
2508
|
+
this._hit_el.setAttribute("x2", x2.toString());
|
|
2509
|
+
this._hit_el.setAttribute("y2", y2.toString());
|
|
2510
|
+
this._hit_el.setAttribute("stroke-width", Math.max(sw, extent * 0.03).toString());
|
|
2511
|
+
this._stampEdge(this._hit_el);
|
|
2154
2512
|
}
|
|
2155
2513
|
} else {
|
|
2156
2514
|
this.svg_element.setAttribute("x1", this.__data__.from.x.toString());
|
|
@@ -2164,10 +2522,11 @@ const _QELine = class _QELine extends Drawable {
|
|
|
2164
2522
|
}
|
|
2165
2523
|
}
|
|
2166
2524
|
draw() {
|
|
2167
|
-
var _a, _b;
|
|
2168
|
-
(_a = this.parent) == null ? void 0 : _a.appendChild(this.
|
|
2525
|
+
var _a, _b, _c;
|
|
2526
|
+
if (this._hit_el) (_a = this.parent) == null ? void 0 : _a.appendChild(this._hit_el);
|
|
2527
|
+
(_b = this.parent) == null ? void 0 : _b.appendChild(this.svg_element);
|
|
2169
2528
|
if (this._arrow_el) {
|
|
2170
|
-
(
|
|
2529
|
+
(_c = this.parent) == null ? void 0 : _c.appendChild(this._arrow_el);
|
|
2171
2530
|
}
|
|
2172
2531
|
}
|
|
2173
2532
|
};
|
|
@@ -2197,14 +2556,45 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2197
2556
|
* (≈ 1–2 % of the coordinate half-extent for a typical ENA network).
|
|
2198
2557
|
*/
|
|
2199
2558
|
computeRadius() {
|
|
2200
|
-
|
|
2201
|
-
|
|
2559
|
+
var _a;
|
|
2560
|
+
const r = this.baseRadius();
|
|
2561
|
+
const cap = (_a = this.__data__.meta) == null ? void 0 : _a.maxRadius;
|
|
2562
|
+
return typeof cap === "number" && cap > 0 ? Math.min(r, cap) : r;
|
|
2563
|
+
}
|
|
2564
|
+
baseRadius() {
|
|
2565
|
+
var _a, _b, _c, _d;
|
|
2566
|
+
const extent = this.graph.__data__.extremes.x_max || 1;
|
|
2567
|
+
const sizeScale = ((_a = this.__data__.meta) == null ? void 0 : _a.sizeScale) ?? 1;
|
|
2568
|
+
if ((_b = this.__data__.meta) == null ? void 0 : _b.fixedSize) {
|
|
2569
|
+
const rect = (_d = (_c = this.graph.svg_element) == null ? void 0 : _c.getBoundingClientRect) == null ? void 0 : _d.call(_c);
|
|
2570
|
+
const zoom = this.graph.zoom_level || 1;
|
|
2571
|
+
const pxPerUnit = rect && rect.width > 0 ? rect.width / (2 * extent) : 1;
|
|
2572
|
+
const targetPx = this.__data__.size * 2 * sizeScale;
|
|
2573
|
+
return targetPx / pxPerUnit / zoom;
|
|
2574
|
+
}
|
|
2575
|
+
return extent * POINT_RADIUS_PER_EXTENT * this.__data__.size * sizeScale;
|
|
2576
|
+
}
|
|
2577
|
+
/** Optional halo stroke (e.g. white outline on unit points to separate
|
|
2578
|
+
* overlapping dots in dense clusters). Width scales with the dot so it holds
|
|
2579
|
+
* up at any zoom. No-op unless the object requests it via meta.stroke. */
|
|
2580
|
+
applyStroke(el) {
|
|
2581
|
+
var _a;
|
|
2582
|
+
const stroke = (_a = this.__data__.meta) == null ? void 0 : _a.stroke;
|
|
2583
|
+
if (!stroke) return;
|
|
2584
|
+
el.setAttribute("stroke", stroke);
|
|
2585
|
+
el.setAttribute("stroke-width", (this.computeRadius() * 0.22).toString());
|
|
2202
2586
|
}
|
|
2203
2587
|
create() {
|
|
2204
2588
|
const tag = this.isSquare ? "rect" : "circle";
|
|
2205
2589
|
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
2206
2590
|
el.setAttribute("data-point-id", this.__data__.ID);
|
|
2591
|
+
const meta = this.__data__.meta ?? {};
|
|
2592
|
+
if (meta.kind) el.setAttribute("data-kind", meta.kind);
|
|
2593
|
+
if (meta.name != null) el.setAttribute("data-name", String(meta.name));
|
|
2594
|
+
if (meta.group != null) el.setAttribute("data-group", String(meta.group));
|
|
2595
|
+
if (meta.unit != null) el.setAttribute("data-unit", String(meta.unit));
|
|
2207
2596
|
el.setAttribute("fill", this.__data__.color.toString());
|
|
2597
|
+
this.applyStroke(el);
|
|
2208
2598
|
const r = this.computeRadius();
|
|
2209
2599
|
if (this.isSquare) {
|
|
2210
2600
|
el.setAttribute("x", (this.x - r).toString());
|
|
@@ -2217,9 +2607,7 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2217
2607
|
el.setAttribute("r", r.toString());
|
|
2218
2608
|
}
|
|
2219
2609
|
this.svg_element = el;
|
|
2220
|
-
el.addEventListener("click", (
|
|
2221
|
-
console.log("Point clicked: ", this.__data__);
|
|
2222
|
-
event.stopPropagation();
|
|
2610
|
+
el.addEventListener("click", () => {
|
|
2223
2611
|
this.__data__.active = !this.__data__.active;
|
|
2224
2612
|
this.update();
|
|
2225
2613
|
const pointEvent = new CustomEvent("point-clicked", {
|
|
@@ -2242,6 +2630,7 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2242
2630
|
}
|
|
2243
2631
|
const r = this.computeRadius();
|
|
2244
2632
|
this.svg_element.setAttribute("fill", this.__data__.color.toString());
|
|
2633
|
+
this.applyStroke(this.svg_element);
|
|
2245
2634
|
if (this.isSquare) {
|
|
2246
2635
|
this.svg_element.setAttribute("x", (this.x - r).toString());
|
|
2247
2636
|
this.svg_element.setAttribute("y", (this.y - r).toString());
|
|
@@ -2260,6 +2649,141 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2260
2649
|
};
|
|
2261
2650
|
__name(_QEPoint, "QEPoint");
|
|
2262
2651
|
let QEPoint = _QEPoint;
|
|
2652
|
+
const _QETrajectory = class _QETrajectory extends Drawable {
|
|
2653
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2654
|
+
super(__data__, parent, graph, __children__);
|
|
2655
|
+
this.__data__ = __data__;
|
|
2656
|
+
this.parent = parent;
|
|
2657
|
+
this.graph = graph;
|
|
2658
|
+
this.__children__ = __children__;
|
|
2659
|
+
this.__name__ = "trajectory";
|
|
2660
|
+
this._path_el = null;
|
|
2661
|
+
this._segments_group = null;
|
|
2662
|
+
this._tip_el = null;
|
|
2663
|
+
this._points_group = null;
|
|
2664
|
+
this.svg_element = this.create();
|
|
2665
|
+
}
|
|
2666
|
+
// Trajectory styling is specified in display pixels, while SVG paths use
|
|
2667
|
+
// graph coordinates. Convert at render time so marks stay legible at any scale.
|
|
2668
|
+
graphUnitsForPixels(pixels) {
|
|
2669
|
+
var _a;
|
|
2670
|
+
const extent = this.graph.__data__.extremes.x_max || 1;
|
|
2671
|
+
const width = ((_a = this.graph.svg_element) == null ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
2672
|
+
return width > 0 ? pixels * (2 * extent / width) : pixels * extent * 6e-3;
|
|
2673
|
+
}
|
|
2674
|
+
create() {
|
|
2675
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2676
|
+
group.setAttribute("class", "qe-trajectory-group");
|
|
2677
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2678
|
+
path.setAttribute("fill", "none");
|
|
2679
|
+
path.setAttribute("stroke", this.__data__.color.toString());
|
|
2680
|
+
path.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
|
|
2681
|
+
group.appendChild(path);
|
|
2682
|
+
this._path_el = path;
|
|
2683
|
+
const segmentsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2684
|
+
segmentsGroup.setAttribute("class", "qe-trajectory-segments");
|
|
2685
|
+
group.appendChild(segmentsGroup);
|
|
2686
|
+
this._segments_group = segmentsGroup;
|
|
2687
|
+
const pointsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2688
|
+
pointsGroup.setAttribute("class", "qe-trajectory-points");
|
|
2689
|
+
group.appendChild(pointsGroup);
|
|
2690
|
+
this._points_group = pointsGroup;
|
|
2691
|
+
if (this.__data__.showTips) {
|
|
2692
|
+
const tip = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
2693
|
+
tip.setAttribute("fill", this.__data__.color.toString());
|
|
2694
|
+
tip.setAttribute("r", this.graphUnitsForPixels(4).toString());
|
|
2695
|
+
tip.setAttribute("stroke", "#ffffff");
|
|
2696
|
+
tip.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
|
|
2697
|
+
group.appendChild(tip);
|
|
2698
|
+
this._tip_el = tip;
|
|
2699
|
+
}
|
|
2700
|
+
this.svg_element = group;
|
|
2701
|
+
return group;
|
|
2702
|
+
}
|
|
2703
|
+
update() {
|
|
2704
|
+
if (!this.svg_element) {
|
|
2705
|
+
this.create();
|
|
2706
|
+
}
|
|
2707
|
+
if (!this.svg_element.parentElement) {
|
|
2708
|
+
this.parent.appendChild(this.svg_element);
|
|
2709
|
+
}
|
|
2710
|
+
const dStr = this.__data__.path;
|
|
2711
|
+
if (this._path_el) {
|
|
2712
|
+
this._path_el.setAttribute("d", dStr);
|
|
2713
|
+
this._path_el.setAttribute("stroke", this.__data__.color.toString());
|
|
2714
|
+
this._path_el.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
|
|
2715
|
+
this._path_el.style.display = this.__data__.lineStyle === "tapered" ? "none" : "block";
|
|
2716
|
+
}
|
|
2717
|
+
if (this._segments_group) {
|
|
2718
|
+
this._segments_group.innerHTML = "";
|
|
2719
|
+
if (this.__data__.lineStyle === "tapered") {
|
|
2720
|
+
const pathPts = this.__data__.pathPoints;
|
|
2721
|
+
const nSegments = pathPts.length - 1;
|
|
2722
|
+
const [minWidth, maxWidth] = this.__data__.strokeWidthRange;
|
|
2723
|
+
const [minOpacity, maxOpacity] = this.__data__.opacityRange;
|
|
2724
|
+
for (let i = 0; i < nSegments; i++) {
|
|
2725
|
+
const start = pathPts[i];
|
|
2726
|
+
const end = pathPts[i + 1];
|
|
2727
|
+
const progress = nSegments <= 1 ? 1 : i / (nSegments - 1);
|
|
2728
|
+
const width = minWidth + (maxWidth - minWidth) * progress;
|
|
2729
|
+
const opacity = minOpacity + (maxOpacity - minOpacity) * progress;
|
|
2730
|
+
const segment = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2731
|
+
segment.setAttribute("d", `M${start.x},${start.y} L${end.x},${end.y}`);
|
|
2732
|
+
segment.setAttribute("fill", "none");
|
|
2733
|
+
segment.setAttribute("stroke", this.__data__.color.toString());
|
|
2734
|
+
segment.setAttribute("stroke-linecap", "round");
|
|
2735
|
+
segment.setAttribute("stroke-linejoin", "round");
|
|
2736
|
+
segment.setAttribute("stroke-width", this.graphUnitsForPixels(width).toString());
|
|
2737
|
+
segment.setAttribute("opacity", opacity.toString());
|
|
2738
|
+
this._segments_group.appendChild(segment);
|
|
2739
|
+
}
|
|
2740
|
+
this._segments_group.style.display = "block";
|
|
2741
|
+
} else {
|
|
2742
|
+
this._segments_group.style.display = "none";
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
const pts = this.__data__.activePoints;
|
|
2746
|
+
if (this._points_group) {
|
|
2747
|
+
if (this.__data__.showPoints && pts.length > 0) {
|
|
2748
|
+
this._points_group.innerHTML = "";
|
|
2749
|
+
pts.forEach((pt) => {
|
|
2750
|
+
var _a;
|
|
2751
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
2752
|
+
dot.setAttribute("cx", pt.x.toString());
|
|
2753
|
+
dot.setAttribute("cy", pt.y.toString());
|
|
2754
|
+
dot.setAttribute("r", this.graphUnitsForPixels(2.5).toString());
|
|
2755
|
+
dot.setAttribute("fill", this.__data__.color.toString());
|
|
2756
|
+
dot.setAttribute("opacity", "0.7");
|
|
2757
|
+
(_a = this._points_group) == null ? void 0 : _a.appendChild(dot);
|
|
2758
|
+
});
|
|
2759
|
+
this._points_group.style.display = "block";
|
|
2760
|
+
} else {
|
|
2761
|
+
this._points_group.style.display = "none";
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
if (this._tip_el && this.__data__.showTips) {
|
|
2765
|
+
if (pts.length > 0) {
|
|
2766
|
+
const lastPt = pts[pts.length - 1];
|
|
2767
|
+
this._tip_el.setAttribute("cx", lastPt.x.toString());
|
|
2768
|
+
this._tip_el.setAttribute("cy", lastPt.y.toString());
|
|
2769
|
+
this._tip_el.setAttribute("fill", this.__data__.color.toString());
|
|
2770
|
+
this._tip_el.setAttribute("r", this.graphUnitsForPixels(4).toString());
|
|
2771
|
+
this._tip_el.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
|
|
2772
|
+
this._tip_el.style.display = "block";
|
|
2773
|
+
} else {
|
|
2774
|
+
this._tip_el.style.display = "none";
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
draw() {
|
|
2779
|
+
if (this.svg_element && !this.svg_element.parentElement) {
|
|
2780
|
+
this.parent.appendChild(this.svg_element);
|
|
2781
|
+
}
|
|
2782
|
+
this.update();
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
__name(_QETrajectory, "QETrajectory");
|
|
2786
|
+
let QETrajectory = _QETrajectory;
|
|
2263
2787
|
const { ArrayTools } = Tools;
|
|
2264
2788
|
const _QEArray = class _QEArray extends Array {
|
|
2265
2789
|
constructor(...args) {
|
|
@@ -2319,7 +2843,8 @@ const QEElementConstructors = {
|
|
|
2319
2843
|
"Edge": QELine,
|
|
2320
2844
|
"TaperedEdge": QELine,
|
|
2321
2845
|
"TaperedWeightedMidpointEdge": QELine,
|
|
2322
|
-
"NodeEdge": QELine
|
|
2846
|
+
"NodeEdge": QELine,
|
|
2847
|
+
"Trajectory": QETrajectory
|
|
2323
2848
|
};
|
|
2324
2849
|
const _QELayer = class _QELayer {
|
|
2325
2850
|
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
@@ -2343,8 +2868,14 @@ const _QELayer = class _QELayer {
|
|
|
2343
2868
|
(obj) => !this.__children__.some((child) => childId(child) === obj.ID)
|
|
2344
2869
|
);
|
|
2345
2870
|
adding.forEach((obj) => {
|
|
2346
|
-
const obj_name = obj.constructor.name;
|
|
2347
|
-
|
|
2871
|
+
const obj_name = obj._typeName || obj.constructor.name;
|
|
2872
|
+
let constructor = QEElementConstructors[obj_name];
|
|
2873
|
+
if (!constructor) {
|
|
2874
|
+
if (obj instanceof Trajectory) constructor = QETrajectory;
|
|
2875
|
+
else if (obj instanceof Point) constructor = QEPoint;
|
|
2876
|
+
else constructor = QELine;
|
|
2877
|
+
}
|
|
2878
|
+
const ena_element = new constructor(obj, this.svg_element, this.graph);
|
|
2348
2879
|
this.__children__.push(ena_element);
|
|
2349
2880
|
});
|
|
2350
2881
|
updating.forEach((child) => {
|
|
@@ -2423,6 +2954,9 @@ const _QELabel = class _QELabel {
|
|
|
2423
2954
|
this.customX = customX;
|
|
2424
2955
|
this.customY = customY;
|
|
2425
2956
|
this._visible = false;
|
|
2957
|
+
this._offsetX = 0;
|
|
2958
|
+
this._offsetY = 0;
|
|
2959
|
+
this.pinned = false;
|
|
2426
2960
|
this.svg_element = this._create();
|
|
2427
2961
|
if (displayMode === Label.DISPLAY_TYPES.ON) {
|
|
2428
2962
|
this._visible = true;
|
|
@@ -2436,10 +2970,52 @@ const _QELabel = class _QELabel {
|
|
|
2436
2970
|
const el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
2437
2971
|
el.textContent = this.text;
|
|
2438
2972
|
el.setAttribute("data-label-for", this.point.ID);
|
|
2439
|
-
el.setAttribute("pointer-events", "
|
|
2973
|
+
el.setAttribute("pointer-events", "auto");
|
|
2974
|
+
el.style.cursor = "grab";
|
|
2975
|
+
this._attachDrag(el);
|
|
2440
2976
|
this.parent.appendChild(el);
|
|
2441
2977
|
return el;
|
|
2442
2978
|
}
|
|
2979
|
+
/** Drag to reposition; pins the label open and emits qe-label-move on drop. */
|
|
2980
|
+
_attachDrag(el) {
|
|
2981
|
+
el.addEventListener("mousedown", (ev) => {
|
|
2982
|
+
ev.preventDefault();
|
|
2983
|
+
ev.stopPropagation();
|
|
2984
|
+
const svg = el.ownerSVGElement;
|
|
2985
|
+
if (!svg) return;
|
|
2986
|
+
const toUser = /* @__PURE__ */ __name((cx, cy) => {
|
|
2987
|
+
const p = svg.createSVGPoint();
|
|
2988
|
+
p.x = cx;
|
|
2989
|
+
p.y = cy;
|
|
2990
|
+
const m = svg.getScreenCTM();
|
|
2991
|
+
return m ? p.matrixTransform(m.inverse()) : { x: 0, y: 0 };
|
|
2992
|
+
}, "toUser");
|
|
2993
|
+
const start = toUser(ev.clientX, ev.clientY);
|
|
2994
|
+
const baseX = this._offsetX, baseY = this._offsetY;
|
|
2995
|
+
el.style.cursor = "grabbing";
|
|
2996
|
+
const onMove = /* @__PURE__ */ __name((e) => {
|
|
2997
|
+
const p = toUser(e.clientX, e.clientY);
|
|
2998
|
+
this._offsetX = baseX + (p.x - start.x);
|
|
2999
|
+
this._offsetY = baseY + (p.y - start.y);
|
|
3000
|
+
this.pinned = true;
|
|
3001
|
+
this._visible = true;
|
|
3002
|
+
this.svg_element.setAttribute("display", "block");
|
|
3003
|
+
this.update();
|
|
3004
|
+
}, "onMove");
|
|
3005
|
+
const onUp = /* @__PURE__ */ __name(() => {
|
|
3006
|
+
document.removeEventListener("mousemove", onMove);
|
|
3007
|
+
document.removeEventListener("mouseup", onUp);
|
|
3008
|
+
el.style.cursor = "grab";
|
|
3009
|
+
this.graph.dispatchEvent(new CustomEvent("qe-label-move", {
|
|
3010
|
+
detail: { id: this.point.ID, transform: { x: this._offsetX, y: this._offsetY } },
|
|
3011
|
+
bubbles: true,
|
|
3012
|
+
composed: true
|
|
3013
|
+
}));
|
|
3014
|
+
}, "onUp");
|
|
3015
|
+
document.addEventListener("mousemove", onMove);
|
|
3016
|
+
document.addEventListener("mouseup", onUp);
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
2443
3019
|
/**
|
|
2444
3020
|
* Recompute position and font-size from current graph extents.
|
|
2445
3021
|
* No-ops when the label is hidden (AUTO/CLICK modes while not hovered/clicked).
|
|
@@ -2488,6 +3064,9 @@ const _QELabel = class _QELabel {
|
|
|
2488
3064
|
}
|
|
2489
3065
|
this.svg_element.setAttribute("x", x.toString());
|
|
2490
3066
|
this.svg_element.setAttribute("y", y.toString());
|
|
3067
|
+
if (this._offsetX || this._offsetY) {
|
|
3068
|
+
this.svg_element.setAttribute("transform", `translate(${this._offsetX}, ${this._offsetY})`);
|
|
3069
|
+
}
|
|
2491
3070
|
this.svg_element.setAttribute("font-size", fontSize.toString());
|
|
2492
3071
|
this.svg_element.setAttribute("font-family", "system-ui, sans-serif");
|
|
2493
3072
|
this.svg_element.setAttribute("font-weight", "500");
|
|
@@ -2501,6 +3080,7 @@ const _QELabel = class _QELabel {
|
|
|
2501
3080
|
this.update();
|
|
2502
3081
|
}
|
|
2503
3082
|
hide() {
|
|
3083
|
+
if (this.pinned) return;
|
|
2504
3084
|
this._visible = false;
|
|
2505
3085
|
this.svg_element.setAttribute("display", "none");
|
|
2506
3086
|
}
|
|
@@ -2536,10 +3116,22 @@ const _QEEdgesElement = class _QEEdgesElement extends HTMLElement {
|
|
|
2536
3116
|
__name(_QEEdgesElement, "QEEdgesElement");
|
|
2537
3117
|
_QEEdgesElement.tagName = "qe-edges";
|
|
2538
3118
|
let QEEdgesElement = _QEEdgesElement;
|
|
3119
|
+
const _QETrajectoriesElement = class _QETrajectoriesElement extends HTMLElement {
|
|
3120
|
+
};
|
|
3121
|
+
__name(_QETrajectoriesElement, "QETrajectoriesElement");
|
|
3122
|
+
_QETrajectoriesElement.tagName = "qe-trajectories";
|
|
3123
|
+
let QETrajectoriesElement = _QETrajectoriesElement;
|
|
3124
|
+
const _QEAxesElement = class _QEAxesElement extends HTMLElement {
|
|
3125
|
+
};
|
|
3126
|
+
__name(_QEAxesElement, "QEAxesElement");
|
|
3127
|
+
_QEAxesElement.tagName = "qe-axes";
|
|
3128
|
+
let QEAxesElement = _QEAxesElement;
|
|
2539
3129
|
customElements.define(QENodesElement.tagName, QENodesElement);
|
|
2540
3130
|
customElements.define(QEMeansElement.tagName, QEMeansElement);
|
|
2541
3131
|
customElements.define(QEPointsElement.tagName, QEPointsElement);
|
|
2542
3132
|
customElements.define(QEEdgesElement.tagName, QEEdgesElement);
|
|
3133
|
+
customElements.define(QETrajectoriesElement.tagName, QETrajectoriesElement);
|
|
3134
|
+
customElements.define(QEAxesElement.tagName, QEAxesElement);
|
|
2543
3135
|
const GROUP_PALETTE = [
|
|
2544
3136
|
"#4477AA",
|
|
2545
3137
|
"#EE6677",
|
|
@@ -2571,6 +3163,14 @@ function computeCIRect(varX, varY, n, pointScale) {
|
|
|
2571
3163
|
return { halfW: t95 * seX, halfH: t95 * seY };
|
|
2572
3164
|
}
|
|
2573
3165
|
__name(computeCIRect, "computeCIRect");
|
|
3166
|
+
const MEAN_MAX_FRACTION_OF_INTERVAL = 0.5;
|
|
3167
|
+
function capMeanToInterval(meanPt, halfW, halfH) {
|
|
3168
|
+
if (!meanPt) return;
|
|
3169
|
+
const half = Math.min(Math.abs(halfW), Math.abs(halfH));
|
|
3170
|
+
if (!(half > 0)) return;
|
|
3171
|
+
meanPt.meta = { ...meanPt.meta ?? {}, maxRadius: half * MEAN_MAX_FRACTION_OF_INTERVAL };
|
|
3172
|
+
}
|
|
3173
|
+
__name(capMeanToInterval, "capMeanToInterval");
|
|
2574
3174
|
function readIntervalBounds(row, dim0, dim1) {
|
|
2575
3175
|
const get = /* @__PURE__ */ __name((col) => {
|
|
2576
3176
|
const i = row.names.indexOf(col);
|
|
@@ -2584,6 +3184,11 @@ function readIntervalBounds(row, dim0, dim1) {
|
|
|
2584
3184
|
return { x_low: xLow, x_high: xHigh, y_low: yLow, y_high: yHigh };
|
|
2585
3185
|
}
|
|
2586
3186
|
__name(readIntervalBounds, "readIntervalBounds");
|
|
3187
|
+
function parseColorScale(attr) {
|
|
3188
|
+
const a = attr == null ? void 0 : attr.trim().toLowerCase();
|
|
3189
|
+
return a === "plot" || a === "absolute" ? a : "model";
|
|
3190
|
+
}
|
|
3191
|
+
__name(parseColorScale, "parseColorScale");
|
|
2587
3192
|
function parseLabelMode(attr, defaultMode) {
|
|
2588
3193
|
switch (attr == null ? void 0 : attr.toLowerCase()) {
|
|
2589
3194
|
case "on":
|
|
@@ -2613,6 +3218,17 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2613
3218
|
this._nodeLabels = /* @__PURE__ */ new Map();
|
|
2614
3219
|
this._meanLabels = /* @__PURE__ */ new Map();
|
|
2615
3220
|
this._pointLabels = /* @__PURE__ */ new Map();
|
|
3221
|
+
this._showAxes = false;
|
|
3222
|
+
this._axisXText = null;
|
|
3223
|
+
this._axisYText = null;
|
|
3224
|
+
this._axisLabelEls = /* @__PURE__ */ new Map();
|
|
3225
|
+
this._magnifyText = null;
|
|
3226
|
+
this._magnifyEl = null;
|
|
3227
|
+
this._animateMs = 0;
|
|
3228
|
+
this._tweenRaf = null;
|
|
3229
|
+
this._highlight = { edges: /* @__PURE__ */ new Set(), nodes: /* @__PURE__ */ new Set() };
|
|
3230
|
+
this._highlightGroup = null;
|
|
3231
|
+
this._tooltipEl = null;
|
|
2616
3232
|
this.graph = this;
|
|
2617
3233
|
this.parent = null;
|
|
2618
3234
|
this.__data__ = new Graph(DefaultGraphOptions);
|
|
@@ -2620,16 +3236,22 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2620
3236
|
this.svg_element = this.create();
|
|
2621
3237
|
console.log("Created ena-graph svg element: ", this.svg_element);
|
|
2622
3238
|
document.addEventListener("model-updated", (e) => {
|
|
2623
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
3239
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
2624
3240
|
if (this._vizena && e.target !== this._vizena) return;
|
|
2625
3241
|
const model = e.detail;
|
|
2626
3242
|
if (!model) return;
|
|
3243
|
+
this._animateMs = this.hasAttribute("animate") ? parseInt(this.getAttribute("animate") || "", 10) || 300 : 0;
|
|
3244
|
+
const tweenFrom = this._animateMs > 0 && this._hasModel ? this._captureTween() : null;
|
|
2627
3245
|
this._hasModel = true;
|
|
2628
3246
|
const dims = model.dimensions;
|
|
2629
3247
|
const unitIdCol = model.unit_id_col;
|
|
2630
3248
|
const nodeIdCol = model.node_id_col;
|
|
2631
3249
|
const groupColName = model.group_col;
|
|
2632
3250
|
const config = this._readLayerConfig();
|
|
3251
|
+
this._showAxes = config.showAxes;
|
|
3252
|
+
this._axisXText = config.axisXLabel === null ? null : config.axisXLabel || String((dims == null ? void 0 : dims[0]) ?? "x");
|
|
3253
|
+
this._magnifyText = config.edgeMagnify == null ? null : `(scaled ${config.edgeMagnify.toFixed(1)}x)`;
|
|
3254
|
+
this._axisYText = config.axisYLabel === null ? null : config.axisYLabel || String((dims == null ? void 0 : dims[1]) ?? "y");
|
|
2633
3255
|
const groupColorMap = {};
|
|
2634
3256
|
if (groupColName && ((_a = model.points) == null ? void 0 : _a.names)) {
|
|
2635
3257
|
const groupCol = model.points.col(groupColName);
|
|
@@ -2663,7 +3285,9 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2663
3285
|
}, "getExtent");
|
|
2664
3286
|
const maxNodeExtent = ((_c = model.nodes) == null ? void 0 : _c.rows) ? getExtent(model.nodes.rows()) : 0;
|
|
2665
3287
|
const maxPointExtent = ((_d = model.points) == null ? void 0 : _d.rows) ? getExtent(model.points.rows()) : 0;
|
|
2666
|
-
const
|
|
3288
|
+
const maxTrajExtent = ((_e = model.trajectories) == null ? void 0 : _e.rows) ? getExtent(model.trajectories.rows()) : 0;
|
|
3289
|
+
const effectivePointExtent = maxPointExtent > 0 ? maxPointExtent : maxTrajExtent;
|
|
3290
|
+
const pointScale = scalePointsEnabled && effectivePointExtent > 0 && maxNodeExtent > 0 ? maxNodeExtent / effectivePointExtent : 1;
|
|
2667
3291
|
const labelNodes = config.labelNodes;
|
|
2668
3292
|
const labelMeans = config.labelMeans;
|
|
2669
3293
|
const labelPoints = config.labelPoints;
|
|
@@ -2686,7 +3310,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2686
3310
|
}, "rowToPoint");
|
|
2687
3311
|
const groupSums = {};
|
|
2688
3312
|
const showUnitPoints = config.showPoints;
|
|
2689
|
-
if ((
|
|
3313
|
+
if ((_f = model.points) == null ? void 0 : _f.rows) {
|
|
2690
3314
|
const pointsLayer = this.__children__.search("__children__", "points");
|
|
2691
3315
|
if (pointsLayer) {
|
|
2692
3316
|
model.points.rows().forEach((row) => {
|
|
@@ -2695,6 +3319,16 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2695
3319
|
const colorStr = group ? groupColorMap[group] ?? "#888888" : "#888888";
|
|
2696
3320
|
if (showUnitPoints) {
|
|
2697
3321
|
const pt = rowToPoint(row, unitIdCol, colorStr, pointScale);
|
|
3322
|
+
pt.size = config.pointSize ?? 2.5;
|
|
3323
|
+
pt.meta = {
|
|
3324
|
+
...pt.meta ?? {},
|
|
3325
|
+
stroke: "#ffffff",
|
|
3326
|
+
kind: "point",
|
|
3327
|
+
name: pt.ID,
|
|
3328
|
+
group: group ?? void 0,
|
|
3329
|
+
unit: pt.ID,
|
|
3330
|
+
fixedSize: config.pointFixedSize
|
|
3331
|
+
};
|
|
2698
3332
|
this.__data__.points.set(pt.ID, pt);
|
|
2699
3333
|
pointsLayer.__data__.objects.set(pt.ID, pt);
|
|
2700
3334
|
}
|
|
@@ -2712,7 +3346,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2712
3346
|
groupSums[group].n += 1;
|
|
2713
3347
|
}
|
|
2714
3348
|
});
|
|
2715
|
-
const hasGroupsFrame = !!(((
|
|
3349
|
+
const hasGroupsFrame = !!(((_g = model.groups) == null ? void 0 : _g.rows) && model.groups.rows().length > 0);
|
|
2716
3350
|
const activeGroupRows = config.showMeans && hasGroupsFrame ? model.groups.rows().filter((r) => {
|
|
2717
3351
|
const g = r.rowName ?? String(r[0]);
|
|
2718
3352
|
return activeMeanGroups.size === 0 || activeMeanGroups.has(g);
|
|
@@ -2744,19 +3378,26 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2744
3378
|
const meanPt = new Point({
|
|
2745
3379
|
ID: `mean-${group}`,
|
|
2746
3380
|
dimensions: [mx, my],
|
|
2747
|
-
meta: { group }
|
|
3381
|
+
meta: { kind: "mean", name: group, group, fixedSize: config.meanFixedSize }
|
|
2748
3382
|
});
|
|
2749
3383
|
meanPt.color = new Color(color);
|
|
2750
3384
|
meanPt.size = 4;
|
|
2751
3385
|
meanPt.shape = "rect";
|
|
2752
3386
|
this.__data__.points.set(meanPt.ID, meanPt);
|
|
2753
3387
|
pointsLayer.__data__.objects.set(meanPt.ID, meanPt);
|
|
2754
|
-
const bounds = readIntervalBounds(row, dims[0], dims[1]);
|
|
2755
|
-
if (bounds) {
|
|
3388
|
+
const bounds = config.showCI ? readIntervalBounds(row, dims[0], dims[1]) : null;
|
|
3389
|
+
if (!bounds) {
|
|
3390
|
+
const stale = this._ciRects.get(group);
|
|
3391
|
+
if (stale) {
|
|
3392
|
+
stale.remove();
|
|
3393
|
+
this._ciRects.delete(group);
|
|
3394
|
+
}
|
|
3395
|
+
} else {
|
|
2756
3396
|
const rect_x = bounds.x_low * pointScale;
|
|
2757
3397
|
const rect_y = -bounds.y_high * pointScale;
|
|
2758
3398
|
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
2759
3399
|
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
3400
|
+
capMeanToInterval(meanPt, rect_width / 2, rect_height / 2);
|
|
2760
3401
|
let el = this._ciRects.get(group);
|
|
2761
3402
|
if (!el) {
|
|
2762
3403
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -2782,7 +3423,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2782
3423
|
const meanPt = new Point({
|
|
2783
3424
|
ID: `mean-${group}`,
|
|
2784
3425
|
dimensions: [x / n * pointScale, -(y / n) * pointScale],
|
|
2785
|
-
meta: { group }
|
|
3426
|
+
meta: { kind: "mean", name: group, group, fixedSize: config.meanFixedSize }
|
|
2786
3427
|
});
|
|
2787
3428
|
meanPt.color = new Color(color);
|
|
2788
3429
|
meanPt.size = 4;
|
|
@@ -2798,7 +3439,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2798
3439
|
this._ciRects.delete(g);
|
|
2799
3440
|
}
|
|
2800
3441
|
});
|
|
2801
|
-
if ((
|
|
3442
|
+
if ((_h = model.confidence) == null ? void 0 : _h.rows) {
|
|
2802
3443
|
model.confidence.rows().forEach((row) => {
|
|
2803
3444
|
const groupIdx = row.names.indexOf(groupColName ?? "group");
|
|
2804
3445
|
const group = groupIdx >= 0 ? String(row[groupIdx]) : row.rowName ?? null;
|
|
@@ -2810,6 +3451,11 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2810
3451
|
const rect_y = -bounds.y_high * pointScale;
|
|
2811
3452
|
const rect_width = (bounds.x_high - bounds.x_low) * pointScale;
|
|
2812
3453
|
const rect_height = (bounds.y_high - bounds.y_low) * pointScale;
|
|
3454
|
+
capMeanToInterval(
|
|
3455
|
+
this.__data__.points.get(`mean-${group}`),
|
|
3456
|
+
rect_width / 2,
|
|
3457
|
+
rect_height / 2
|
|
3458
|
+
);
|
|
2813
3459
|
let el = this._ciRects.get(group);
|
|
2814
3460
|
if (!el) {
|
|
2815
3461
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -2837,6 +3483,11 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2837
3483
|
const rawVarY = y2 / n - μy * μy;
|
|
2838
3484
|
const { halfW, halfH } = computeCIRect(rawVarX, rawVarY, n, pointScale);
|
|
2839
3485
|
const cx = μx * pointScale, cy = -μy * pointScale;
|
|
3486
|
+
capMeanToInterval(
|
|
3487
|
+
this.__data__.points.get(`mean-${group}`),
|
|
3488
|
+
halfW,
|
|
3489
|
+
halfH
|
|
3490
|
+
);
|
|
2840
3491
|
let el = this._ciRects.get(group);
|
|
2841
3492
|
if (!el) {
|
|
2842
3493
|
el = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
@@ -2858,7 +3509,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2858
3509
|
});
|
|
2859
3510
|
}
|
|
2860
3511
|
}
|
|
2861
|
-
if ((
|
|
3512
|
+
if ((_i = model.outlier) == null ? void 0 : _i.rows) {
|
|
2862
3513
|
this._outlierRects.forEach((el, g) => {
|
|
2863
3514
|
if (!groupColorMap[g]) {
|
|
2864
3515
|
el.remove();
|
|
@@ -2947,20 +3598,74 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2947
3598
|
pointsLayer.__data__.__state__.setVariable("layer_updated", pointsLayer.__data__);
|
|
2948
3599
|
}
|
|
2949
3600
|
}
|
|
3601
|
+
const trajectoriesLayer = this.__children__.search("__children__", "trajectories");
|
|
3602
|
+
const trajectoriesEnabled = config.showTrajectories;
|
|
3603
|
+
if (trajectoriesEnabled && trajectoriesLayer) {
|
|
3604
|
+
trajectoriesLayer.__data__.objects.clear();
|
|
3605
|
+
this.__data__.trajectories.clear();
|
|
3606
|
+
const trajSource = ((_j = model.trajectories) == null ? void 0 : _j.rows) ? model.trajectories : model.points;
|
|
3607
|
+
if (trajSource == null ? void 0 : trajSource.rows) {
|
|
3608
|
+
const unitRows = {};
|
|
3609
|
+
const selectedUnits = config.trajectoryUnits ? config.trajectoryUnits.split(",").map((s) => s.trim()) : null;
|
|
3610
|
+
trajSource.rows().forEach((row) => {
|
|
3611
|
+
const uIdx = row.names.indexOf(unitIdCol) >= 0 ? row.names.indexOf(unitIdCol) : row.names.indexOf("unit") >= 0 ? row.names.indexOf("unit") : row.names.indexOf("ENA_UNIT") >= 0 ? row.names.indexOf("ENA_UNIT") : -1;
|
|
3612
|
+
const unit = uIdx >= 0 ? String(row[uIdx]) : row.rowName || "unit-1";
|
|
3613
|
+
if (selectedUnits && !selectedUnits.includes(unit)) return;
|
|
3614
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
3615
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
3616
|
+
const tIdx = row.names.indexOf("time") >= 0 ? row.names.indexOf("time") : row.names.indexOf("ActivityNumber") >= 0 ? row.names.indexOf("ActivityNumber") : row.names.indexOf("RowID") >= 0 ? row.names.indexOf("RowID") : -1;
|
|
3617
|
+
const time = tIdx >= 0 ? Number(row[tIdx]) : unitRows[unit] ? unitRows[unit].length + 1 : 1;
|
|
3618
|
+
if (xIdx >= 0 && yIdx >= 0) {
|
|
3619
|
+
if (!unitRows[unit]) unitRows[unit] = [];
|
|
3620
|
+
unitRows[unit].push({
|
|
3621
|
+
x: Number(row[xIdx]) * pointScale,
|
|
3622
|
+
y: -Number(row[yIdx]) * pointScale,
|
|
3623
|
+
time
|
|
3624
|
+
});
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
const unitKeys = Object.keys(unitRows);
|
|
3628
|
+
unitKeys.forEach((unitKey, uIdx) => {
|
|
3629
|
+
const rawPts = unitRows[unitKey];
|
|
3630
|
+
if (rawPts.length === 0) return;
|
|
3631
|
+
const colorStr = GROUP_PALETTE[uIdx % GROUP_PALETTE.length];
|
|
3632
|
+
const trajObj = new Trajectory({
|
|
3633
|
+
ID: `traj-${unitKey}`,
|
|
3634
|
+
unitId: unitKey,
|
|
3635
|
+
color: new Color(colorStr),
|
|
3636
|
+
lineStyle: config.trajectoryLineStyle,
|
|
3637
|
+
strokeWidth: config.trajectoryStrokeWidth,
|
|
3638
|
+
strokeWidthRange: config.trajectoryStrokeWidthRange,
|
|
3639
|
+
opacityRange: config.trajectoryOpacityRange,
|
|
3640
|
+
smooth: config.trajectorySmooth,
|
|
3641
|
+
maxDegree: config.trajectoryMaxDegree,
|
|
3642
|
+
degreeSelection: config.trajectoryDegreeSelection,
|
|
3643
|
+
currentTime: config.trajectoryPlayhead ?? Infinity,
|
|
3644
|
+
showPoints: config.trajectoryShowPoints,
|
|
3645
|
+
showTips: config.trajectoryShowTips,
|
|
3646
|
+
points: rawPts.map((p) => ({ x: p.x, y: p.y, time: p.time }))
|
|
3647
|
+
});
|
|
3648
|
+
this.__data__.trajectories.set(unitKey, trajObj);
|
|
3649
|
+
trajectoriesLayer.__data__.objects.set(trajObj.ID, trajObj);
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
trajectoriesLayer.__data__.__state__.setVariable("layer_updated", trajectoriesLayer.__data__);
|
|
3653
|
+
}
|
|
2950
3654
|
const networkLayer = this.__children__.search("__children__", "network");
|
|
2951
3655
|
const edgeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edges");
|
|
2952
3656
|
const nodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "nodes");
|
|
2953
3657
|
const edgenodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edgenodes");
|
|
2954
3658
|
const edgesEnabled = config.showEdges;
|
|
2955
|
-
if (((
|
|
3659
|
+
if (((_k = model.nodes) == null ? void 0 : _k.rows) && nodeLayer) {
|
|
2956
3660
|
model.nodes.rows().forEach((row) => {
|
|
2957
3661
|
const pt = rowToPoint(row, nodeIdCol, "#444444");
|
|
2958
3662
|
pt.size = 2;
|
|
3663
|
+
pt.meta = { ...pt.meta ?? {}, kind: "node", name: pt.ID };
|
|
2959
3664
|
nodeLayer.__data__.objects.set(pt.ID, pt);
|
|
2960
3665
|
});
|
|
2961
|
-
if (edgesEnabled && edgeLayer && ((
|
|
3666
|
+
if (edgesEnabled && edgeLayer && ((_l = model.edges) == null ? void 0 : _l.rows)) {
|
|
2962
3667
|
const unitGroupMap = {};
|
|
2963
|
-
if (groupColName && ((
|
|
3668
|
+
if (groupColName && ((_m = model.points) == null ? void 0 : _m.rows)) {
|
|
2964
3669
|
model.points.rows().forEach((row) => {
|
|
2965
3670
|
const qIdx = row.names.indexOf(unitIdCol);
|
|
2966
3671
|
const gIdx = row.names.indexOf(groupColName);
|
|
@@ -3054,21 +3759,46 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3054
3759
|
});
|
|
3055
3760
|
}
|
|
3056
3761
|
const isDirected = !!model.directed;
|
|
3057
|
-
const
|
|
3058
|
-
const
|
|
3059
|
-
|
|
3060
|
-
|
|
3762
|
+
const widthFrac = widthPerWeight(config.edgeWidth) * (config.edgeMagnify ?? 1);
|
|
3763
|
+
const rangeOf = /* @__PURE__ */ __name((ws) => {
|
|
3764
|
+
const nz = ws.map(Math.abs).filter((v) => v > 0);
|
|
3765
|
+
return nz.length ? [Math.min(...nz), Math.max(...nz)] : [0, 0];
|
|
3766
|
+
}, "rangeOf");
|
|
3767
|
+
const modelRange = /* @__PURE__ */ __name((self) => {
|
|
3768
|
+
const vals = [];
|
|
3769
|
+
edgePairs.forEach(({ col, pair }) => {
|
|
3770
|
+
if (pair[0] === pair[1] !== self) return;
|
|
3771
|
+
const column = model.edges.col(col);
|
|
3772
|
+
if (column) for (const v of column) vals.push(Number(v));
|
|
3773
|
+
});
|
|
3774
|
+
return rangeOf(vals);
|
|
3775
|
+
}, "modelRange");
|
|
3776
|
+
const plotRange = /* @__PURE__ */ __name((self) => rangeOf(drawEdges.filter((e2) => e2.pair[0] === e2.pair[1] === self).map((e2) => e2.weight)), "plotRange");
|
|
3777
|
+
const cscale = config.edgeColorScale;
|
|
3778
|
+
const colourRange = /* @__PURE__ */ __name((self) => cscale === "plot" ? plotRange(self) : modelRange(self), "colourRange");
|
|
3779
|
+
const lineRange = colourRange(false), selfRange = colourRange(true);
|
|
3780
|
+
const colourOf = /* @__PURE__ */ __name((w, self) => {
|
|
3781
|
+
if (cscale === "absolute") {
|
|
3782
|
+
const a = Math.min(1, Math.abs(w));
|
|
3783
|
+
return { saturation: a, opacity: a };
|
|
3784
|
+
}
|
|
3785
|
+
const [lo, hi] = self ? selfRange : lineRange;
|
|
3786
|
+
return { saturation: rampFor(w, lo, hi, SATURATION_RANGE), opacity: rampFor(w, lo, hi, OPACITY_RANGE) };
|
|
3787
|
+
}, "colourOf");
|
|
3061
3788
|
const nodeWeightSum = {};
|
|
3062
3789
|
drawEdges.forEach(({ pair, weight }) => {
|
|
3063
3790
|
const isSelf = pair[0] === pair[1];
|
|
3064
3791
|
if (isSelf && !isDirected) return;
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3792
|
+
const add = /* @__PURE__ */ __name((id) => {
|
|
3793
|
+
nodeWeightSum[id] = (nodeWeightSum[id] ?? 0) + Math.abs(weight);
|
|
3794
|
+
}, "add");
|
|
3795
|
+
if (isDirected) {
|
|
3796
|
+
add(pair[1]);
|
|
3797
|
+
return;
|
|
3068
3798
|
}
|
|
3799
|
+
add(pair[0]);
|
|
3800
|
+
add(pair[1]);
|
|
3069
3801
|
});
|
|
3070
|
-
const maxNodeWeight = Math.max(1, ...Object.values(nodeWeightSum));
|
|
3071
|
-
const nodeSizes = {};
|
|
3072
3802
|
model.nodes.rows().forEach((row) => {
|
|
3073
3803
|
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3074
3804
|
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
@@ -3076,8 +3806,12 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3076
3806
|
const pt = nodeLayer.__data__.objects.get(id);
|
|
3077
3807
|
if (!pt) return;
|
|
3078
3808
|
const w = nodeWeightSum[id] ?? 0;
|
|
3079
|
-
|
|
3080
|
-
|
|
3809
|
+
const unconnected = !(w > 0);
|
|
3810
|
+
pt.size = unconnected ? config.hideUnconnectedNodes ? 0 : UNCONNECTED_NODE_SIZE : nodeSizeForEdges(w, widthFrac);
|
|
3811
|
+
pt.meta = {
|
|
3812
|
+
...pt.meta ?? {},
|
|
3813
|
+
hidden: unconnected && config.hideUnconnectedNodes
|
|
3814
|
+
};
|
|
3081
3815
|
});
|
|
3082
3816
|
const totalWeightByPair = {};
|
|
3083
3817
|
if (isDirected) {
|
|
@@ -3086,15 +3820,13 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3086
3820
|
totalWeightByPair[key] = (totalWeightByPair[key] ?? 0) + weight;
|
|
3087
3821
|
});
|
|
3088
3822
|
}
|
|
3089
|
-
((_l = this.__data__.extremes) == null ? void 0 : _l.x_max) ?? 1;
|
|
3090
3823
|
drawEdges.forEach(({ col, pair, weight, color }) => {
|
|
3091
3824
|
const fromNode = nodeLayer.__data__.objects.get(pair[0]);
|
|
3092
3825
|
const toNode = nodeLayer.__data__.objects.get(pair[1]);
|
|
3093
3826
|
if (!fromNode || !toNode) return;
|
|
3094
3827
|
const isSelf = pair[0] === pair[1];
|
|
3095
3828
|
if (isSelf && !isDirected) return;
|
|
3096
|
-
const
|
|
3097
|
-
const opacity = Math.min(1, weight / classMax);
|
|
3829
|
+
const { saturation, opacity } = colourOf(weight, isSelf);
|
|
3098
3830
|
const fromPt = new Point({ ID: `${pair[0]}-src`, dimensions: [fromNode.x, fromNode.y], meta: {} });
|
|
3099
3831
|
const toPt = new Point({ ID: `${pair[1]}-dst`, dimensions: [toNode.x, toNode.y], meta: {} });
|
|
3100
3832
|
let edgeLine;
|
|
@@ -3118,16 +3850,17 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3118
3850
|
edgeLine.meta = {
|
|
3119
3851
|
isMeanEdge: true,
|
|
3120
3852
|
weight,
|
|
3121
|
-
//
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
maxWeight: isSelf ? maxSelfWeight : maxEdgeWeight,
|
|
3853
|
+
// Width per unit weight (extent units), shared by all marks.
|
|
3854
|
+
widthFrac,
|
|
3855
|
+
saturation,
|
|
3125
3856
|
opacity,
|
|
3126
3857
|
isDirected,
|
|
3127
3858
|
isSelf,
|
|
3128
|
-
//
|
|
3129
|
-
|
|
3130
|
-
|
|
3859
|
+
// Identity for the qe-edge-* interaction events.
|
|
3860
|
+
source: pair[0],
|
|
3861
|
+
target: pair[1],
|
|
3862
|
+
group: groupAttr ?? void 0,
|
|
3863
|
+
unit: unitAttr ?? void 0
|
|
3131
3864
|
};
|
|
3132
3865
|
const targetLayer = isSelf ? edgenodeLayer ?? edgeLayer : edgeLayer;
|
|
3133
3866
|
targetLayer.__data__.objects.set(edgeLine.ID, edgeLine);
|
|
@@ -3135,7 +3868,15 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3135
3868
|
edgeLayer.__data__.__state__.setVariable("layer_updated", edgeLayer.__data__);
|
|
3136
3869
|
if (edgenodeLayer) edgenodeLayer.__data__.__state__.setVariable("layer_updated", edgenodeLayer.__data__);
|
|
3137
3870
|
}
|
|
3138
|
-
|
|
3871
|
+
const nodesVisible = config.showNodes;
|
|
3872
|
+
if (nodeLayer.svg_element) {
|
|
3873
|
+
nodeLayer.svg_element.style.display = nodesVisible ? "" : "none";
|
|
3874
|
+
}
|
|
3875
|
+
if (!nodesVisible) {
|
|
3876
|
+
this._nodeLabels.forEach((lbl) => lbl.remove());
|
|
3877
|
+
this._nodeLabels.clear();
|
|
3878
|
+
}
|
|
3879
|
+
if (labelSvg && labelNodes !== 0 && nodesVisible) {
|
|
3139
3880
|
this._nodeLabels.forEach((lbl, id) => {
|
|
3140
3881
|
if (!nodeLayer.__data__.objects.get(id)) {
|
|
3141
3882
|
lbl.remove();
|
|
@@ -3143,11 +3884,17 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3143
3884
|
}
|
|
3144
3885
|
});
|
|
3145
3886
|
model.nodes.rows().forEach((row) => {
|
|
3887
|
+
var _a2, _b2;
|
|
3146
3888
|
const codeIdx = row.names.indexOf(nodeIdCol);
|
|
3147
3889
|
const id = codeIdx >= 0 ? String(row[codeIdx]) : "";
|
|
3148
3890
|
if (!id) return;
|
|
3149
3891
|
const pt = nodeLayer.__data__.objects.get(id);
|
|
3150
3892
|
if (!pt) return;
|
|
3893
|
+
if ((_a2 = pt.meta) == null ? void 0 : _a2.hidden) {
|
|
3894
|
+
(_b2 = this._nodeLabels.get(id)) == null ? void 0 : _b2.remove();
|
|
3895
|
+
this._nodeLabels.delete(id);
|
|
3896
|
+
return;
|
|
3897
|
+
}
|
|
3151
3898
|
let lbl = this._nodeLabels.get(id);
|
|
3152
3899
|
if (!lbl) {
|
|
3153
3900
|
lbl = new QELabel(id, pt, labelSvg, this, labelNodes);
|
|
@@ -3164,6 +3911,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3164
3911
|
if (axisPointsLayer) this.__data__.__state__.setVariable("graph_updated_axis_points", axisPointsLayer.__data__.objects);
|
|
3165
3912
|
if (axisLinesLayer) this.__data__.__state__.setVariable("graph_updated_axis_lines", axisLinesLayer.__data__.objects);
|
|
3166
3913
|
this.update();
|
|
3914
|
+
if (tweenFrom) this._runTween(tweenFrom);
|
|
3167
3915
|
});
|
|
3168
3916
|
}
|
|
3169
3917
|
static get observedAttributes() {
|
|
@@ -3204,17 +3952,27 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3204
3952
|
// should render. Called once at the top of every model-updated handler so
|
|
3205
3953
|
// all rendering decisions are derived from a single snapshot.
|
|
3206
3954
|
_readLayerConfig() {
|
|
3955
|
+
var _a;
|
|
3207
3956
|
const nodesEl = this.querySelector("qe-nodes");
|
|
3208
3957
|
const meansEl = this.querySelector("qe-means");
|
|
3209
3958
|
const pointsEl = this.querySelector("qe-points");
|
|
3210
3959
|
const edgesEl = this.querySelector("qe-edges");
|
|
3960
|
+
const trajectoriesEl = this.querySelector("qe-trajectories");
|
|
3961
|
+
const axesEl = this.querySelector("qe-axes");
|
|
3211
3962
|
const labelsShorthand = this.getAttribute("labels");
|
|
3212
3963
|
return {
|
|
3213
|
-
// Nodes — always
|
|
3964
|
+
// Nodes — always placed from the model (they anchor the coordinate
|
|
3965
|
+
// space); <qe-nodes> presence draws them, like every other layer.
|
|
3214
3966
|
labelNodes: parseLabelMode(
|
|
3215
3967
|
(nodesEl == null ? void 0 : nodesEl.getAttribute("label")) ?? this.getAttribute("label-nodes") ?? labelsShorthand,
|
|
3216
3968
|
Label.DISPLAY_TYPES.ON
|
|
3217
3969
|
),
|
|
3970
|
+
showNodes: !!nodesEl,
|
|
3971
|
+
edgeWidth: (() => {
|
|
3972
|
+
const v = Number(this.getAttribute("edge-width"));
|
|
3973
|
+
return Number.isFinite(v) && v > 0 && v <= 1 ? v : DEFAULT_EDGE_WIDTH;
|
|
3974
|
+
})(),
|
|
3975
|
+
hideUnconnectedNodes: ((_a = nodesEl == null ? void 0 : nodesEl.getAttribute("unconnected")) == null ? void 0 : _a.toLowerCase()) === "hide",
|
|
3218
3976
|
scalePoints: this.getAttribute("scale-points") !== "false",
|
|
3219
3977
|
// Means — presence of <qe-means> = show; absence = hide.
|
|
3220
3978
|
showMeans: !!meansEl,
|
|
@@ -3224,20 +3982,56 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3224
3982
|
(meansEl == null ? void 0 : meansEl.getAttribute("label")) ?? this.getAttribute("label-means") ?? labelsShorthand,
|
|
3225
3983
|
Label.DISPLAY_TYPES.ON
|
|
3226
3984
|
),
|
|
3985
|
+
// fixed-size keeps mean markers a constant on-screen px size (webENA parity).
|
|
3986
|
+
meanFixedSize: !!meansEl && meansEl.hasAttribute("fixed-size"),
|
|
3227
3987
|
// Points — presence of <qe-points> = show unit dots.
|
|
3228
3988
|
showPoints: !!pointsEl,
|
|
3229
3989
|
labelPoints: parseLabelMode(
|
|
3230
3990
|
(pointsEl == null ? void 0 : pointsEl.getAttribute("label")) ?? this.getAttribute("label-points") ?? labelsShorthand,
|
|
3231
3991
|
Label.DISPLAY_TYPES.OFF
|
|
3232
3992
|
),
|
|
3993
|
+
pointSize: (pointsEl == null ? void 0 : pointsEl.getAttribute("size")) ? parseFloat(pointsEl.getAttribute("size")) : null,
|
|
3994
|
+
// fixed-size keeps unit dots a constant on-screen px size (webENA parity).
|
|
3995
|
+
pointFixedSize: !!pointsEl && pointsEl.hasAttribute("fixed-size"),
|
|
3233
3996
|
// Edges — presence of <qe-edges> = draw a network.
|
|
3234
3997
|
showEdges: !!edgesEl,
|
|
3235
3998
|
edgeGroup: (edgesEl == null ? void 0 : edgesEl.getAttribute("group")) ?? null,
|
|
3236
3999
|
edgeUnit: (edgesEl == null ? void 0 : edgesEl.getAttribute("unit")) ?? null,
|
|
3237
4000
|
edgeCompare: (edgesEl == null ? void 0 : edgesEl.getAttribute("compare")) ?? null,
|
|
3238
|
-
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null
|
|
4001
|
+
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null,
|
|
4002
|
+
edgeColorScale: parseColorScale((edgesEl == null ? void 0 : edgesEl.getAttribute("color-scale")) ?? null),
|
|
4003
|
+
edgeMagnify: (edgesEl == null ? void 0 : edgesEl.hasAttribute("magnify")) ? (() => {
|
|
4004
|
+
const v = Number(edgesEl.getAttribute("magnify"));
|
|
4005
|
+
return Number.isFinite(v) && v > 0 ? v : 1;
|
|
4006
|
+
})() : null,
|
|
4007
|
+
// Trajectories — presence of <qe-trajectories> = draw cognitive trajectories
|
|
4008
|
+
showTrajectories: !!trajectoriesEl,
|
|
4009
|
+
trajectoryUnits: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("units")) ?? null,
|
|
4010
|
+
trajectorySmooth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("smooth")) ?? "none",
|
|
4011
|
+
trajectoryMaxDegree: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("max-degree")) ? parseInt(trajectoriesEl.getAttribute("max-degree"), 10) : 3,
|
|
4012
|
+
trajectoryDegreeSelection: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("degree-selection")) ?? "loocv_2d",
|
|
4013
|
+
trajectoryShowPoints: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("points")) !== "false",
|
|
4014
|
+
trajectoryShowTips: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("tips")) !== "false",
|
|
4015
|
+
trajectoryPlayhead: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("playhead")) ? parseFloat(trajectoriesEl.getAttribute("playhead")) : null,
|
|
4016
|
+
trajectoryLineStyle: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("line-style")) ?? "uniform",
|
|
4017
|
+
trajectoryStrokeWidth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width")) ? parseFloat(trajectoriesEl.getAttribute("stroke-width")) : 2,
|
|
4018
|
+
trajectoryStrokeWidthRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width-range"), [1, 6]),
|
|
4019
|
+
trajectoryOpacityRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("opacity-range"), [0.3, 1]),
|
|
4020
|
+
// Axes — presence of <qe-axes> = draw axis lines. The x / y attributes add
|
|
4021
|
+
// a per-axis label: a value is used verbatim, an empty attribute falls back
|
|
4022
|
+
// to the dimension name (resolved later, where dims are known). null = no
|
|
4023
|
+
// label for that axis.
|
|
4024
|
+
showAxes: !!axesEl,
|
|
4025
|
+
axisXLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("x")) ? axesEl.getAttribute("x") ?? "" : null,
|
|
4026
|
+
axisYLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("y")) ? axesEl.getAttribute("y") ?? "" : null
|
|
3239
4027
|
};
|
|
3240
4028
|
}
|
|
4029
|
+
_parseNumberPair(value, fallback) {
|
|
4030
|
+
if (!value) return fallback;
|
|
4031
|
+
const parts = value.split(",").map((part) => Number(part.trim()));
|
|
4032
|
+
if (parts.length < 2 || !parts.every(Number.isFinite)) return fallback;
|
|
4033
|
+
return [parts[0], parts[1]];
|
|
4034
|
+
}
|
|
3241
4035
|
attributeChangedCallback(_name2, oldValue, newValue) {
|
|
3242
4036
|
if (oldValue === newValue) return;
|
|
3243
4037
|
this._scheduleRerender();
|
|
@@ -3246,9 +4040,11 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3246
4040
|
var _a;
|
|
3247
4041
|
const w = this.getAttribute("width");
|
|
3248
4042
|
const h = this.getAttribute("height");
|
|
4043
|
+
this.style.display = "block";
|
|
3249
4044
|
if (w) this.style.width = isNaN(Number(w)) ? w : `${w}px`;
|
|
3250
4045
|
if (h) this.style.height = isNaN(Number(h)) ? h : `${h}px`;
|
|
3251
4046
|
this.appendChild(this.svg_element);
|
|
4047
|
+
_QEGraphElement._instances.add(this);
|
|
3252
4048
|
this._childObserver = new MutationObserver(() => this._scheduleRerender());
|
|
3253
4049
|
this._childObserver.observe(this, {
|
|
3254
4050
|
childList: true,
|
|
@@ -3259,14 +4055,32 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3259
4055
|
"unit",
|
|
3260
4056
|
"compare",
|
|
3261
4057
|
"also",
|
|
4058
|
+
"color-scale",
|
|
4059
|
+
"magnify",
|
|
3262
4060
|
"groups",
|
|
3263
4061
|
"confidence",
|
|
3264
4062
|
"label",
|
|
4063
|
+
"unconnected",
|
|
3265
4064
|
"scale-points",
|
|
4065
|
+
"edge-width",
|
|
3266
4066
|
"labels",
|
|
3267
4067
|
"label-nodes",
|
|
3268
4068
|
"label-means",
|
|
3269
|
-
"label-points"
|
|
4069
|
+
"label-points",
|
|
4070
|
+
"units",
|
|
4071
|
+
"smooth",
|
|
4072
|
+
"degree",
|
|
4073
|
+
"points",
|
|
4074
|
+
"tips",
|
|
4075
|
+
"playhead",
|
|
4076
|
+
"line-style",
|
|
4077
|
+
"stroke-width",
|
|
4078
|
+
"stroke-width-range",
|
|
4079
|
+
"opacity-range",
|
|
4080
|
+
"x",
|
|
4081
|
+
"y",
|
|
4082
|
+
"size",
|
|
4083
|
+
"fixed-size"
|
|
3270
4084
|
]
|
|
3271
4085
|
});
|
|
3272
4086
|
const forId = this.getAttribute("for");
|
|
@@ -3286,13 +4100,283 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3286
4100
|
}
|
|
3287
4101
|
}
|
|
3288
4102
|
disconnectedCallback() {
|
|
3289
|
-
var _a;
|
|
4103
|
+
var _a, _b;
|
|
3290
4104
|
(_a = this._childObserver) == null ? void 0 : _a.disconnect();
|
|
3291
4105
|
this._childObserver = null;
|
|
4106
|
+
_QEGraphElement._instances.delete(this);
|
|
4107
|
+
(_b = this._tooltipEl) == null ? void 0 : _b.remove();
|
|
4108
|
+
this._tooltipEl = null;
|
|
4109
|
+
}
|
|
4110
|
+
/** All connected <qe-graph> instances (newest membership order). */
|
|
4111
|
+
static instances() {
|
|
4112
|
+
return [..._QEGraphElement._instances];
|
|
4113
|
+
}
|
|
4114
|
+
/** Look up a graph by its `id` or `plot-group` attribute. */
|
|
4115
|
+
static getGraph(key) {
|
|
4116
|
+
for (const g of _QEGraphElement._instances) {
|
|
4117
|
+
if (g.id === key || g.getAttribute("plot-group") === key) return g;
|
|
4118
|
+
}
|
|
4119
|
+
return null;
|
|
4120
|
+
}
|
|
4121
|
+
/** Resolved code nodes with their current graph-space coordinates. */
|
|
4122
|
+
getNodes() {
|
|
4123
|
+
const layer = this.__children__.search("__children__", "nodes");
|
|
4124
|
+
if (!layer) return [];
|
|
4125
|
+
return [...layer.__data__.objects.values()].map((o) => {
|
|
4126
|
+
var _a;
|
|
4127
|
+
return {
|
|
4128
|
+
id: o.ID,
|
|
4129
|
+
name: ((_a = o.meta) == null ? void 0 : _a.name) ?? o.ID,
|
|
4130
|
+
x: o.x,
|
|
4131
|
+
y: o.y
|
|
4132
|
+
};
|
|
4133
|
+
});
|
|
4134
|
+
}
|
|
4135
|
+
/** Resolved edges of the current network with weights and direction. */
|
|
4136
|
+
getEdges() {
|
|
4137
|
+
const collect = /* @__PURE__ */ __name((name) => {
|
|
4138
|
+
const l = this.__children__.search("__children__", name);
|
|
4139
|
+
return l ? [...l.__data__.objects.values()] : [];
|
|
4140
|
+
}, "collect");
|
|
4141
|
+
return [...collect("edges"), ...collect("edgenodes")].map((o) => {
|
|
4142
|
+
const m = o.meta ?? {};
|
|
4143
|
+
return {
|
|
4144
|
+
source: m.source,
|
|
4145
|
+
target: m.target,
|
|
4146
|
+
weight: m.weight ?? 0,
|
|
4147
|
+
directed: !!m.isDirected,
|
|
4148
|
+
self: !!m.isSelf
|
|
4149
|
+
};
|
|
4150
|
+
}).filter((e) => e.source != null && e.target != null);
|
|
4151
|
+
}
|
|
4152
|
+
/** Marks (circles/rects), undirected edge lines, and directed / self-edge
|
|
4153
|
+
* triangle paths all participate in tweening. */
|
|
4154
|
+
_tweenTargets() {
|
|
4155
|
+
return [...this.svg_element.querySelectorAll(
|
|
4156
|
+
"circle[data-point-id], rect[data-point-id], line[data-edge-source], path[data-edge-source]"
|
|
4157
|
+
)];
|
|
4158
|
+
}
|
|
4159
|
+
_readCoords(el) {
|
|
4160
|
+
const tag = el.tagName.toLowerCase();
|
|
4161
|
+
const n = /* @__PURE__ */ __name((a) => parseFloat(el.getAttribute(a) || "0"), "n");
|
|
4162
|
+
if (tag === "circle") return { t: "c", cx: n("cx"), cy: n("cy") };
|
|
4163
|
+
if (tag === "rect") return { t: "r", x: n("x"), y: n("y") };
|
|
4164
|
+
if (tag === "line") return { t: "l", x1: n("x1"), y1: n("y1"), x2: n("x2"), y2: n("y2") };
|
|
4165
|
+
if (tag === "path") {
|
|
4166
|
+
const d = el.getAttribute("d") || "";
|
|
4167
|
+
return { t: "p", d, nums: (d.match(_QEGraphElement._PATH_NUM_RE) || []).map(Number) };
|
|
4168
|
+
}
|
|
4169
|
+
return null;
|
|
4170
|
+
}
|
|
4171
|
+
/** Snapshot current tween-target positions keyed by element. */
|
|
4172
|
+
_captureTween() {
|
|
4173
|
+
const m = /* @__PURE__ */ new Map();
|
|
4174
|
+
this._tweenTargets().forEach((el) => {
|
|
4175
|
+
const c = this._readCoords(el);
|
|
4176
|
+
if (c) m.set(el, c);
|
|
4177
|
+
});
|
|
4178
|
+
return m;
|
|
4179
|
+
}
|
|
4180
|
+
/** Animate each still-present element from its snapshot to its new position. */
|
|
4181
|
+
_runTween(from) {
|
|
4182
|
+
if (this._tweenRaf !== null) {
|
|
4183
|
+
cancelAnimationFrame(this._tweenRaf);
|
|
4184
|
+
this._tweenRaf = null;
|
|
4185
|
+
}
|
|
4186
|
+
const to = /* @__PURE__ */ new Map();
|
|
4187
|
+
this._tweenTargets().forEach((el) => {
|
|
4188
|
+
if (from.has(el)) {
|
|
4189
|
+
const c = this._readCoords(el);
|
|
4190
|
+
if (c) to.set(el, c);
|
|
4191
|
+
}
|
|
4192
|
+
});
|
|
4193
|
+
if (!to.size) return;
|
|
4194
|
+
const dur = this._animateMs;
|
|
4195
|
+
const t0 = performance.now();
|
|
4196
|
+
const ease = /* @__PURE__ */ __name((t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2, "ease");
|
|
4197
|
+
const lerp = /* @__PURE__ */ __name((a, b, k) => a + (b - a) * k, "lerp");
|
|
4198
|
+
const step = /* @__PURE__ */ __name((now) => {
|
|
4199
|
+
const t = Math.min(1, (now - t0) / dur);
|
|
4200
|
+
const k = ease(t);
|
|
4201
|
+
to.forEach((nc, el) => {
|
|
4202
|
+
const oc = from.get(el);
|
|
4203
|
+
if (!oc || oc.t !== nc.t) return;
|
|
4204
|
+
if (nc.t === "c") {
|
|
4205
|
+
el.setAttribute("cx", String(lerp(oc.cx, nc.cx, k)));
|
|
4206
|
+
el.setAttribute("cy", String(lerp(oc.cy, nc.cy, k)));
|
|
4207
|
+
} else if (nc.t === "r") {
|
|
4208
|
+
el.setAttribute("x", String(lerp(oc.x, nc.x, k)));
|
|
4209
|
+
el.setAttribute("y", String(lerp(oc.y, nc.y, k)));
|
|
4210
|
+
} else if (nc.t === "l") {
|
|
4211
|
+
el.setAttribute("x1", String(lerp(oc.x1, nc.x1, k)));
|
|
4212
|
+
el.setAttribute("y1", String(lerp(oc.y1, nc.y1, k)));
|
|
4213
|
+
el.setAttribute("x2", String(lerp(oc.x2, nc.x2, k)));
|
|
4214
|
+
el.setAttribute("y2", String(lerp(oc.y2, nc.y2, k)));
|
|
4215
|
+
} else {
|
|
4216
|
+
if (!oc.nums || oc.nums.length !== nc.nums.length) return;
|
|
4217
|
+
let i = 0;
|
|
4218
|
+
const d = nc.d.replace(
|
|
4219
|
+
_QEGraphElement._PATH_NUM_RE,
|
|
4220
|
+
() => String(lerp(oc.nums[i], nc.nums[i++], k))
|
|
4221
|
+
);
|
|
4222
|
+
el.setAttribute("d", d);
|
|
4223
|
+
}
|
|
4224
|
+
});
|
|
4225
|
+
this._tweenRaf = t < 1 ? requestAnimationFrame(step) : null;
|
|
4226
|
+
}, "step");
|
|
4227
|
+
this._tweenRaf = requestAnimationFrame(step);
|
|
4228
|
+
}
|
|
4229
|
+
/** Serialize the current plot as a standalone SVG string (webENA P2.3). */
|
|
4230
|
+
toSVG() {
|
|
4231
|
+
const svg = this.svg_element.cloneNode(true);
|
|
4232
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
4233
|
+
svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
4234
|
+
const rect = this.svg_element.getBoundingClientRect();
|
|
4235
|
+
if (rect.width) svg.setAttribute("width", String(Math.round(rect.width)));
|
|
4236
|
+
if (rect.height) svg.setAttribute("height", String(Math.round(rect.height)));
|
|
4237
|
+
return new XMLSerializer().serializeToString(svg);
|
|
4238
|
+
}
|
|
4239
|
+
/** Rasterize the current plot to a PNG data URL (webENA P2.3). `scale`
|
|
4240
|
+
* multiplies the pixel dimensions for a higher-resolution image. */
|
|
4241
|
+
toPNG(scale = 2) {
|
|
4242
|
+
const svgStr = this.toSVG();
|
|
4243
|
+
const rect = this.svg_element.getBoundingClientRect();
|
|
4244
|
+
const w = Math.max(1, Math.round(rect.width));
|
|
4245
|
+
const h = Math.max(1, Math.round(rect.height));
|
|
4246
|
+
return new Promise((resolve, reject) => {
|
|
4247
|
+
const blob = new Blob([svgStr], { type: "image/svg+xml;charset=utf-8" });
|
|
4248
|
+
const url = URL.createObjectURL(blob);
|
|
4249
|
+
const img = new Image();
|
|
4250
|
+
img.onload = () => {
|
|
4251
|
+
try {
|
|
4252
|
+
const canvas = document.createElement("canvas");
|
|
4253
|
+
canvas.width = w * scale;
|
|
4254
|
+
canvas.height = h * scale;
|
|
4255
|
+
const ctx = canvas.getContext("2d");
|
|
4256
|
+
if (!ctx) {
|
|
4257
|
+
URL.revokeObjectURL(url);
|
|
4258
|
+
reject(new Error("no 2d context"));
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
ctx.scale(scale, scale);
|
|
4262
|
+
ctx.drawImage(img, 0, 0, w, h);
|
|
4263
|
+
URL.revokeObjectURL(url);
|
|
4264
|
+
resolve(canvas.toDataURL("image/png"));
|
|
4265
|
+
} catch (err) {
|
|
4266
|
+
URL.revokeObjectURL(url);
|
|
4267
|
+
reject(err);
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
img.onerror = (e) => {
|
|
4271
|
+
URL.revokeObjectURL(url);
|
|
4272
|
+
reject(e);
|
|
4273
|
+
};
|
|
4274
|
+
img.src = url;
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
/** Unit points with their current graph-space coordinates. */
|
|
4278
|
+
getPoints() {
|
|
4279
|
+
const layer = this.__children__.search("__children__", "points");
|
|
4280
|
+
if (!layer) return [];
|
|
4281
|
+
return [...layer.__data__.objects.values()].filter((o) => {
|
|
4282
|
+
var _a;
|
|
4283
|
+
return ((_a = o.meta) == null ? void 0 : _a.kind) === "point";
|
|
4284
|
+
}).map((o) => {
|
|
4285
|
+
var _a;
|
|
4286
|
+
return { id: o.ID, group: (_a = o.meta) == null ? void 0 : _a.group, x: o.x, y: o.y };
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
/** Ids of unit points inside a graph-space rectangle (for drag/lasso select). */
|
|
4290
|
+
pointsInRect(rect) {
|
|
4291
|
+
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);
|
|
4292
|
+
}
|
|
4293
|
+
/** Order-independent key so an undirected pair matches either direction. */
|
|
4294
|
+
_edgeKey(a, b) {
|
|
4295
|
+
return a <= b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
4296
|
+
}
|
|
4297
|
+
/** Mark edges/nodes as highlighted. Replaces the previous selection. */
|
|
4298
|
+
setHighlight(sel) {
|
|
4299
|
+
this._highlight.edges = new Set((sel.edges ?? []).map((e) => this._edgeKey(e.source, e.target)));
|
|
4300
|
+
this._highlight.nodes = new Set((sel.nodes ?? []).map(String));
|
|
4301
|
+
this._applyHighlights();
|
|
4302
|
+
}
|
|
4303
|
+
/** Clear all highlighting. */
|
|
4304
|
+
clearHighlight() {
|
|
4305
|
+
this._highlight.edges.clear();
|
|
4306
|
+
this._highlight.nodes.clear();
|
|
4307
|
+
this._applyHighlights();
|
|
4308
|
+
}
|
|
4309
|
+
_applyHighlights() {
|
|
4310
|
+
if (!this.svg_element) return;
|
|
4311
|
+
if (!this._highlightGroup) {
|
|
4312
|
+
this._highlightGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
4313
|
+
this._highlightGroup.setAttribute("data-layer-name", "highlight");
|
|
4314
|
+
this._highlightGroup.setAttribute("pointer-events", "none");
|
|
4315
|
+
}
|
|
4316
|
+
this.svg_element.appendChild(this._highlightGroup);
|
|
4317
|
+
const g = this._highlightGroup;
|
|
4318
|
+
g.textContent = "";
|
|
4319
|
+
const extent = this.__data__.extremes.x_max || 1;
|
|
4320
|
+
const HL = "#f5a623";
|
|
4321
|
+
const mk = /* @__PURE__ */ __name((tag, attrs) => {
|
|
4322
|
+
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
4323
|
+
for (const k in attrs) el.setAttribute(k, String(attrs[k]));
|
|
4324
|
+
return el;
|
|
4325
|
+
}, "mk");
|
|
4326
|
+
if (this._highlight.edges.size) {
|
|
4327
|
+
const xy = {};
|
|
4328
|
+
this.getNodes().forEach((n) => {
|
|
4329
|
+
xy[n.id] = { x: n.x, y: n.y };
|
|
4330
|
+
});
|
|
4331
|
+
this._highlight.edges.forEach((key) => {
|
|
4332
|
+
const [a, b] = key.split("\0");
|
|
4333
|
+
const A = xy[a], B = xy[b];
|
|
4334
|
+
if (!A || !B) return;
|
|
4335
|
+
g.appendChild(mk("line", {
|
|
4336
|
+
x1: A.x,
|
|
4337
|
+
y1: A.y,
|
|
4338
|
+
x2: B.x,
|
|
4339
|
+
y2: B.y,
|
|
4340
|
+
stroke: HL,
|
|
4341
|
+
"stroke-width": extent * 0.02,
|
|
4342
|
+
"stroke-opacity": 0.6,
|
|
4343
|
+
"stroke-linecap": "round"
|
|
4344
|
+
}));
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
this._highlight.nodes.forEach((id) => {
|
|
4348
|
+
var _a;
|
|
4349
|
+
const el = this.svg_element.querySelector(`[data-point-id="${((_a = window.CSS) == null ? void 0 : _a.escape) ? CSS.escape(id) : id}"]`);
|
|
4350
|
+
if (!el) return;
|
|
4351
|
+
const tag = el.tagName.toLowerCase();
|
|
4352
|
+
let cx, cy, r;
|
|
4353
|
+
if (tag === "rect") {
|
|
4354
|
+
const w = parseFloat(el.getAttribute("width") || "0");
|
|
4355
|
+
cx = parseFloat(el.getAttribute("x") || "0") + w / 2;
|
|
4356
|
+
cy = parseFloat(el.getAttribute("y") || "0") + parseFloat(el.getAttribute("height") || "0") / 2;
|
|
4357
|
+
r = w / 2;
|
|
4358
|
+
} else {
|
|
4359
|
+
cx = parseFloat(el.getAttribute("cx") || "0");
|
|
4360
|
+
cy = parseFloat(el.getAttribute("cy") || "0");
|
|
4361
|
+
r = parseFloat(el.getAttribute("r") || "0");
|
|
4362
|
+
}
|
|
4363
|
+
g.appendChild(mk("circle", {
|
|
4364
|
+
cx,
|
|
4365
|
+
cy,
|
|
4366
|
+
r: r * 1.7 + extent * 4e-3,
|
|
4367
|
+
fill: "none",
|
|
4368
|
+
stroke: HL,
|
|
4369
|
+
"stroke-width": extent * 6e-3
|
|
4370
|
+
}));
|
|
4371
|
+
});
|
|
3292
4372
|
}
|
|
3293
4373
|
create() {
|
|
3294
4374
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3295
4375
|
svg.classList.add("graph");
|
|
4376
|
+
svg.style.display = "block";
|
|
4377
|
+
svg.style.width = "100%";
|
|
4378
|
+
svg.style.height = "100%";
|
|
4379
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
3296
4380
|
svg.setAttribute("viewBox", this.__data__.extremes.as_viewBox());
|
|
3297
4381
|
svg.setAttribute("transform", this.__data__.transform.toString());
|
|
3298
4382
|
svg.setAttribute("role", "figure");
|
|
@@ -3402,6 +4486,138 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3402
4486
|
const lbl = getAutoLabel(id);
|
|
3403
4487
|
if ((lbl == null ? void 0 : lbl.displayMode) === Label.DISPLAY_TYPES.AUTO) lbl.hide();
|
|
3404
4488
|
});
|
|
4489
|
+
this._attachInteractionEvents();
|
|
4490
|
+
}
|
|
4491
|
+
/** Emit a namespaced CustomEvent on the public element (bubbles + composed). */
|
|
4492
|
+
_emit(name, detail) {
|
|
4493
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
4494
|
+
}
|
|
4495
|
+
/** Walk up from a hit element to the nearest ancestor carrying `attr`. */
|
|
4496
|
+
_closestWith(el, attr) {
|
|
4497
|
+
var _a, _b;
|
|
4498
|
+
while (el && el !== this.svg_element) {
|
|
4499
|
+
if (((_a = el.getAttribute) == null ? void 0 : _a.call(el, attr)) != null && ((_b = el.hasAttribute) == null ? void 0 : _b.call(el, attr))) return el;
|
|
4500
|
+
el = el.parentElement;
|
|
4501
|
+
}
|
|
4502
|
+
return null;
|
|
4503
|
+
}
|
|
4504
|
+
/** Resolve an edge hit element to a qe-edge-* detail, or null. */
|
|
4505
|
+
_edgeDetail(el) {
|
|
4506
|
+
const e = this._closestWith(el, "data-edge-source");
|
|
4507
|
+
if (!e) return null;
|
|
4508
|
+
const d = {
|
|
4509
|
+
source: e.getAttribute("data-edge-source"),
|
|
4510
|
+
target: e.getAttribute("data-edge-target"),
|
|
4511
|
+
weight: parseFloat(e.getAttribute("data-edge-weight") || "0"),
|
|
4512
|
+
directed: e.getAttribute("data-edge-directed") === "true",
|
|
4513
|
+
self: e.getAttribute("data-edge-self") === "true"
|
|
4514
|
+
};
|
|
4515
|
+
if (e.hasAttribute("data-edge-group")) d.group = e.getAttribute("data-edge-group");
|
|
4516
|
+
if (e.hasAttribute("data-edge-unit")) d.unit = e.getAttribute("data-edge-unit");
|
|
4517
|
+
return d;
|
|
4518
|
+
}
|
|
4519
|
+
/** Resolve a node/mean/point hit element to a qe-node-* detail, or null. */
|
|
4520
|
+
_nodeDetail(el) {
|
|
4521
|
+
const n = this._closestWith(el, "data-point-id");
|
|
4522
|
+
if (!n) return null;
|
|
4523
|
+
const tag = n.tagName.toLowerCase();
|
|
4524
|
+
let x, y;
|
|
4525
|
+
if (tag === "rect") {
|
|
4526
|
+
x = parseFloat(n.getAttribute("x") || "0") + parseFloat(n.getAttribute("width") || "0") / 2;
|
|
4527
|
+
y = parseFloat(n.getAttribute("y") || "0") + parseFloat(n.getAttribute("height") || "0") / 2;
|
|
4528
|
+
} else {
|
|
4529
|
+
x = parseFloat(n.getAttribute("cx") || "0");
|
|
4530
|
+
y = parseFloat(n.getAttribute("cy") || "0");
|
|
4531
|
+
}
|
|
4532
|
+
const id = n.getAttribute("data-point-id");
|
|
4533
|
+
const d = {
|
|
4534
|
+
id,
|
|
4535
|
+
kind: n.getAttribute("data-kind") || "point",
|
|
4536
|
+
name: n.getAttribute("data-name") || id,
|
|
4537
|
+
x,
|
|
4538
|
+
y
|
|
4539
|
+
};
|
|
4540
|
+
if (n.hasAttribute("data-group")) d.group = n.getAttribute("data-group");
|
|
4541
|
+
if (n.hasAttribute("data-unit")) d.unit = n.getAttribute("data-unit");
|
|
4542
|
+
return d;
|
|
4543
|
+
}
|
|
4544
|
+
_attachInteractionEvents() {
|
|
4545
|
+
const svg = this.svg_element;
|
|
4546
|
+
svg.addEventListener("click", (ev) => {
|
|
4547
|
+
const target = ev.target;
|
|
4548
|
+
const edge = this._edgeDetail(target);
|
|
4549
|
+
if (edge) {
|
|
4550
|
+
this._emit("qe-edge-click", { ...edge, originalEvent: ev });
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
const node = this._nodeDetail(target);
|
|
4554
|
+
if (node) this._emit("qe-node-click", { ...node, originalEvent: ev });
|
|
4555
|
+
});
|
|
4556
|
+
let hovered = null;
|
|
4557
|
+
const keyOf = /* @__PURE__ */ __name((t, d) => t === "edge" ? `${d.source}\0${d.target}` : String(d.id), "keyOf");
|
|
4558
|
+
svg.addEventListener("mousemove", (ev) => {
|
|
4559
|
+
const target = ev.target;
|
|
4560
|
+
const edge = this._edgeDetail(target);
|
|
4561
|
+
const node = edge ? null : this._nodeDetail(target);
|
|
4562
|
+
const next = edge ? { type: "edge", detail: edge } : node ? { type: "node", detail: node } : null;
|
|
4563
|
+
const nextKey = next ? keyOf(next.type, next.detail) : null;
|
|
4564
|
+
if (hovered && (!next || hovered.key !== nextKey)) {
|
|
4565
|
+
this._emit(hovered.type === "edge" ? "qe-edge-unhover" : "qe-node-unhover", { key: hovered.key });
|
|
4566
|
+
hovered = null;
|
|
4567
|
+
}
|
|
4568
|
+
if (next && (!hovered || hovered.key !== nextKey)) {
|
|
4569
|
+
this._emit(next.type === "edge" ? "qe-edge-hover" : "qe-node-hover", { ...next.detail, originalEvent: ev });
|
|
4570
|
+
hovered = { type: next.type, key: nextKey };
|
|
4571
|
+
}
|
|
4572
|
+
if (this.hasAttribute("tooltip") && next) {
|
|
4573
|
+
this._showTooltip(this._tooltipText(next.type, next.detail), ev.clientX, ev.clientY);
|
|
4574
|
+
} else {
|
|
4575
|
+
this._hideTooltip();
|
|
4576
|
+
}
|
|
4577
|
+
});
|
|
4578
|
+
svg.addEventListener("mouseleave", () => {
|
|
4579
|
+
if (hovered) {
|
|
4580
|
+
this._emit(hovered.type === "edge" ? "qe-edge-unhover" : "qe-node-unhover", { key: hovered.key });
|
|
4581
|
+
hovered = null;
|
|
4582
|
+
}
|
|
4583
|
+
this._hideTooltip();
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
/** "source – target (weight)" for edges; the mark name for nodes/means/points. */
|
|
4587
|
+
_tooltipText(type, d) {
|
|
4588
|
+
if (type === "edge") {
|
|
4589
|
+
const w = Number(d.weight);
|
|
4590
|
+
const wStr = Number.isFinite(w) ? ` (${+w.toFixed(3)})` : "";
|
|
4591
|
+
return `${d.source} – ${d.target}${wStr}`;
|
|
4592
|
+
}
|
|
4593
|
+
return String(d.name ?? d.id);
|
|
4594
|
+
}
|
|
4595
|
+
_showTooltip(text, clientX, clientY) {
|
|
4596
|
+
if (!this._tooltipEl) {
|
|
4597
|
+
const el = document.createElement("div");
|
|
4598
|
+
el.setAttribute("data-qe-tooltip", "");
|
|
4599
|
+
Object.assign(el.style, {
|
|
4600
|
+
position: "fixed",
|
|
4601
|
+
zIndex: "2147483647",
|
|
4602
|
+
pointerEvents: "none",
|
|
4603
|
+
background: "rgba(20,20,20,0.92)",
|
|
4604
|
+
color: "#fff",
|
|
4605
|
+
font: "12px/1.4 system-ui, sans-serif",
|
|
4606
|
+
padding: "3px 7px",
|
|
4607
|
+
borderRadius: "4px",
|
|
4608
|
+
whiteSpace: "nowrap",
|
|
4609
|
+
transform: "translate(-50%, calc(-100% - 8px))"
|
|
4610
|
+
});
|
|
4611
|
+
document.body.appendChild(el);
|
|
4612
|
+
this._tooltipEl = el;
|
|
4613
|
+
}
|
|
4614
|
+
this._tooltipEl.textContent = text;
|
|
4615
|
+
this._tooltipEl.style.left = `${clientX}px`;
|
|
4616
|
+
this._tooltipEl.style.top = `${clientY}px`;
|
|
4617
|
+
this._tooltipEl.style.display = "block";
|
|
4618
|
+
}
|
|
4619
|
+
_hideTooltip() {
|
|
4620
|
+
if (this._tooltipEl) this._tooltipEl.style.display = "none";
|
|
3405
4621
|
}
|
|
3406
4622
|
draw() {
|
|
3407
4623
|
var _a;
|
|
@@ -3419,6 +4635,78 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3419
4635
|
this.update_layers();
|
|
3420
4636
|
this.update_labels();
|
|
3421
4637
|
this.update_extremes();
|
|
4638
|
+
this.update_axes();
|
|
4639
|
+
this._applyHighlights();
|
|
4640
|
+
}
|
|
4641
|
+
/**
|
|
4642
|
+
* Axes (<qe-axes>): toggle the axis-line cross by presence, and draw/position
|
|
4643
|
+
* per-axis labels at the positive ends using the current extent. Labels are
|
|
4644
|
+
* cached SVG <text> nodes in the labels group; null text removes them.
|
|
4645
|
+
*/
|
|
4646
|
+
update_axes() {
|
|
4647
|
+
var _a;
|
|
4648
|
+
const axesLayer = this.__children__.search("__children__", "axes");
|
|
4649
|
+
if (axesLayer == null ? void 0 : axesLayer.svg_element) {
|
|
4650
|
+
axesLayer.svg_element.style.display = this._showAxes ? "" : "none";
|
|
4651
|
+
}
|
|
4652
|
+
const parent = this._labelsGroup;
|
|
4653
|
+
const extent = this.__data__.extremes.x_max;
|
|
4654
|
+
if (!parent || extent <= 0) return;
|
|
4655
|
+
const fontSize = extent * 0.06;
|
|
4656
|
+
const gap = fontSize * 0.6;
|
|
4657
|
+
const place = /* @__PURE__ */ __name((axis, text) => {
|
|
4658
|
+
let el = this._axisLabelEls.get(axis);
|
|
4659
|
+
if (!this._showAxes || text == null) {
|
|
4660
|
+
if (el) {
|
|
4661
|
+
el.remove();
|
|
4662
|
+
this._axisLabelEls.delete(axis);
|
|
4663
|
+
}
|
|
4664
|
+
return;
|
|
4665
|
+
}
|
|
4666
|
+
if (!el) {
|
|
4667
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
4668
|
+
el.setAttribute("data-axis-label", axis);
|
|
4669
|
+
el.setAttribute("pointer-events", "none");
|
|
4670
|
+
parent.appendChild(el);
|
|
4671
|
+
this._axisLabelEls.set(axis, el);
|
|
4672
|
+
}
|
|
4673
|
+
el.textContent = text;
|
|
4674
|
+
el.setAttribute("font-size", fontSize.toString());
|
|
4675
|
+
el.setAttribute("font-family", "system-ui, sans-serif");
|
|
4676
|
+
el.setAttribute("font-weight", "600");
|
|
4677
|
+
el.setAttribute("fill", "#556");
|
|
4678
|
+
if (axis === "x") {
|
|
4679
|
+
el.setAttribute("x", (extent - gap).toString());
|
|
4680
|
+
el.setAttribute("y", (-gap).toString());
|
|
4681
|
+
el.setAttribute("text-anchor", "end");
|
|
4682
|
+
} else {
|
|
4683
|
+
el.setAttribute("x", gap.toString());
|
|
4684
|
+
el.setAttribute("y", (-extent + fontSize + gap).toString());
|
|
4685
|
+
el.setAttribute("text-anchor", "start");
|
|
4686
|
+
}
|
|
4687
|
+
}, "place");
|
|
4688
|
+
place("x", this._axisXText);
|
|
4689
|
+
place("y", this._axisYText);
|
|
4690
|
+
if (this._magnifyText == null) {
|
|
4691
|
+
(_a = this._magnifyEl) == null ? void 0 : _a.remove();
|
|
4692
|
+
this._magnifyEl = null;
|
|
4693
|
+
} else {
|
|
4694
|
+
if (!this._magnifyEl) {
|
|
4695
|
+
this._magnifyEl = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
4696
|
+
this._magnifyEl.setAttribute("data-magnify-label", "");
|
|
4697
|
+
this._magnifyEl.setAttribute("pointer-events", "none");
|
|
4698
|
+
}
|
|
4699
|
+
if (!this._magnifyEl.parentElement) parent.appendChild(this._magnifyEl);
|
|
4700
|
+
const el = this._magnifyEl;
|
|
4701
|
+
el.textContent = this._magnifyText;
|
|
4702
|
+
el.setAttribute("font-size", (fontSize * 0.85).toString());
|
|
4703
|
+
el.setAttribute("font-family", "system-ui, sans-serif");
|
|
4704
|
+
el.setAttribute("font-style", "italic");
|
|
4705
|
+
el.setAttribute("fill", "#556");
|
|
4706
|
+
el.setAttribute("x", (-extent + gap).toString());
|
|
4707
|
+
el.setAttribute("y", (-extent + fontSize + gap).toString());
|
|
4708
|
+
el.setAttribute("text-anchor", "start");
|
|
4709
|
+
}
|
|
3422
4710
|
}
|
|
3423
4711
|
update_labels() {
|
|
3424
4712
|
this._nodeLabels.forEach((lbl) => lbl.update());
|
|
@@ -3482,8 +4770,12 @@ _QEGraphElement.style = `
|
|
|
3482
4770
|
background-color: #FFF;
|
|
3483
4771
|
}
|
|
3484
4772
|
`;
|
|
4773
|
+
_QEGraphElement._instances = /* @__PURE__ */ new Set();
|
|
4774
|
+
_QEGraphElement._PATH_NUM_RE = /-?\d*\.?\d+(?:e[-+]?\d+)?/gi;
|
|
3485
4775
|
let QEGraphElement = _QEGraphElement;
|
|
3486
4776
|
customElements.define(QEGraphElement.tagName, QEGraphElement);
|
|
4777
|
+
const getGraph = /* @__PURE__ */ __name((key) => QEGraphElement.getGraph(key), "getGraph");
|
|
4778
|
+
const getGraphs = /* @__PURE__ */ __name(() => QEGraphElement.instances(), "getGraphs");
|
|
3487
4779
|
export {
|
|
3488
4780
|
Color,
|
|
3489
4781
|
DataFrame,
|
|
@@ -3492,6 +4784,7 @@ export {
|
|
|
3492
4784
|
DataFrameRow,
|
|
3493
4785
|
Edge,
|
|
3494
4786
|
Graph,
|
|
4787
|
+
QEAxesElement,
|
|
3495
4788
|
QEEdgesElement,
|
|
3496
4789
|
QEGraphElement,
|
|
3497
4790
|
QELayer,
|
|
@@ -3500,8 +4793,18 @@ export {
|
|
|
3500
4793
|
QENodesElement,
|
|
3501
4794
|
QEPoint,
|
|
3502
4795
|
QEPointsElement,
|
|
4796
|
+
QETrajectoriesElement,
|
|
4797
|
+
QETrajectory,
|
|
3503
4798
|
QEVisualElement,
|
|
4799
|
+
TRAJECTORY_ZERO_EPS,
|
|
4800
|
+
Trajectory,
|
|
3504
4801
|
WeightedColor,
|
|
4802
|
+
fitTrajectory,
|
|
4803
|
+
getGraph,
|
|
4804
|
+
getGraphs,
|
|
4805
|
+
nearOriginFilter,
|
|
4806
|
+
sampleCurve,
|
|
4807
|
+
selectDegree,
|
|
3505
4808
|
vizENA
|
|
3506
4809
|
};
|
|
3507
4810
|
//# sourceMappingURL=qeviz.es.js.map
|