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