@qe-libs/qeviz 0.1.0 → 0.2.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 +16 -0
- package/dist/qeviz.es.js +624 -25
- package/dist/qeviz.es.js.map +1 -1
- package/dist/qeviz.umd.js +624 -25
- package/dist/qeviz.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/qeviz.es.js
CHANGED
|
@@ -160,7 +160,7 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
__privateSet(this, _names2, names);
|
|
163
|
-
if (rowNames && rowNames.length !== this[0].length) {
|
|
163
|
+
if (rowNames && this[0] && rowNames.length !== this[0].length) {
|
|
164
164
|
throw new Error("Row names length must match the number of rows in the DataFrame.");
|
|
165
165
|
}
|
|
166
166
|
if (rowNames && rowNames.length > 0) {
|
|
@@ -224,6 +224,9 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
224
224
|
rows(...wh) {
|
|
225
225
|
let selectors, selectedRows = [], selectedRowNames = [];
|
|
226
226
|
if (wh.length === 0) {
|
|
227
|
+
if (!this[0] || this.length === 0) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
227
230
|
const numRows = this[0].length;
|
|
228
231
|
selectedRows = Array.from({ length: numRows }, (_, i) => this.getRowByIndex(i));
|
|
229
232
|
selectedRowNames = __privateGet(this, _rowNames2);
|
|
@@ -282,7 +285,7 @@ const _DataFrame = class _DataFrame extends Array {
|
|
|
282
285
|
return __privateGet(this, _rowNames2);
|
|
283
286
|
}
|
|
284
287
|
get nrow() {
|
|
285
|
-
return this[0].length;
|
|
288
|
+
return this[0] ? this[0].length : 0;
|
|
286
289
|
}
|
|
287
290
|
get ncol() {
|
|
288
291
|
return this.length;
|
|
@@ -349,6 +352,12 @@ const _Model = class _Model {
|
|
|
349
352
|
set groups(v) {
|
|
350
353
|
this._groups = v;
|
|
351
354
|
}
|
|
355
|
+
get trajectories() {
|
|
356
|
+
return this._trajectories;
|
|
357
|
+
}
|
|
358
|
+
set trajectories(v) {
|
|
359
|
+
this._trajectories = v;
|
|
360
|
+
}
|
|
352
361
|
get dimensions() {
|
|
353
362
|
return this._dimensions;
|
|
354
363
|
}
|
|
@@ -461,7 +470,7 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
461
470
|
*/
|
|
462
471
|
setModelData(data) {
|
|
463
472
|
var _a, _b, _c, _d;
|
|
464
|
-
const frameKeys = ["nodes", "edges", "points", "groups", "confidence", "outlier"];
|
|
473
|
+
const frameKeys = ["nodes", "edges", "points", "groups", "confidence", "outlier", "trajectories"];
|
|
465
474
|
frameKeys.forEach((name) => {
|
|
466
475
|
var _a2;
|
|
467
476
|
const raw = data[name];
|
|
@@ -470,7 +479,7 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
470
479
|
let idCol;
|
|
471
480
|
if (name === "nodes") {
|
|
472
481
|
idCol = data.node_id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
473
|
-
} else if (name === "edges" || name === "points") {
|
|
482
|
+
} else if (name === "edges" || name === "points" || name === "trajectories") {
|
|
474
483
|
idCol = data.id_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
475
484
|
} else {
|
|
476
485
|
idCol = data.group_col ?? colNames.find((c) => (types[c] ?? "character") === "character");
|
|
@@ -540,8 +549,12 @@ const _QEVisualElement = class _QEVisualElement extends HTMLElement {
|
|
|
540
549
|
__name(_QEVisualElement, "QEVisualElement");
|
|
541
550
|
_QEVisualElement.tagName = "qe-visual";
|
|
542
551
|
_QEVisualElement.styles = `
|
|
543
|
-
:host { display:
|
|
552
|
+
:host { display: block; width: 100%; }
|
|
544
553
|
:host(.hidden) { display: none !important; }
|
|
554
|
+
/* The output wrapper and the slotted graph fill the host so the plot
|
|
555
|
+
stretches to its container instead of collapsing to intrinsic size. */
|
|
556
|
+
div { display: block; width: 100%; height: 100%; }
|
|
557
|
+
::slotted(qe-graph) { display: block; width: 100%; height: 100%; }
|
|
545
558
|
`;
|
|
546
559
|
let QEVisualElement = _QEVisualElement;
|
|
547
560
|
customElements.define(QEVisualElement.tagName, QEVisualElement);
|
|
@@ -812,7 +825,9 @@ var Tools;
|
|
|
812
825
|
__name(slopeIntercept, "slopeIntercept");
|
|
813
826
|
Line2.slopeIntercept = slopeIntercept;
|
|
814
827
|
function point_along(line, distance = 0) {
|
|
815
|
-
|
|
828
|
+
const ratio = distance;
|
|
829
|
+
const Xt = (1 - ratio) * line[0][0] + ratio * line[1][0];
|
|
830
|
+
const Yt = (1 - ratio) * line[0][1] + ratio * line[1][1];
|
|
816
831
|
return [Xt, Yt];
|
|
817
832
|
}
|
|
818
833
|
__name(point_along, "point_along");
|
|
@@ -1659,6 +1674,7 @@ const _Graph = class _Graph {
|
|
|
1659
1674
|
this.points = /* @__PURE__ */ new Map();
|
|
1660
1675
|
this.nodes = /* @__PURE__ */ new Map();
|
|
1661
1676
|
this.edges = /* @__PURE__ */ new Map();
|
|
1677
|
+
this.trajectories = /* @__PURE__ */ new Map();
|
|
1662
1678
|
this.labels = /* @__PURE__ */ new Map();
|
|
1663
1679
|
this.transform = new Transform();
|
|
1664
1680
|
this.scale_x = 1;
|
|
@@ -1701,6 +1717,7 @@ const _Graph = class _Graph {
|
|
|
1701
1717
|
{ name: "edgenodes", layers: [], tabindex: 0 }
|
|
1702
1718
|
]
|
|
1703
1719
|
},
|
|
1720
|
+
{ name: "trajectories", layers: [], tabindex: 0 },
|
|
1704
1721
|
{ name: "points", layers: [], tabindex: 0 },
|
|
1705
1722
|
{ name: "labels", layers: [], tabindex: 0 }
|
|
1706
1723
|
] }
|
|
@@ -1826,9 +1843,13 @@ const _Graph = class _Graph {
|
|
|
1826
1843
|
if (search_layers.length === 0) {
|
|
1827
1844
|
this.extremes = new Extremes(1, 1);
|
|
1828
1845
|
} else {
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1846
|
+
const content_max = Math.max(
|
|
1847
|
+
...search_layers.map((l) => Math.max(
|
|
1848
|
+
...[...l.objects.values()].map((o) => Math.max(Math.abs(o.x), Math.abs(o.y)))
|
|
1849
|
+
)).filter((a) => Math.abs(a) !== Infinity)
|
|
1850
|
+
);
|
|
1851
|
+
const found_max = (Number.isFinite(content_max) && content_max > 0 ? content_max : 1) * 1.3;
|
|
1852
|
+
this.extremes = new Extremes(found_max, found_max);
|
|
1832
1853
|
}
|
|
1833
1854
|
}
|
|
1834
1855
|
base_point_size(scale = 8) {
|
|
@@ -1868,6 +1889,251 @@ _DrawableElement.style = `
|
|
|
1868
1889
|
}
|
|
1869
1890
|
`;
|
|
1870
1891
|
let DrawableElement = _DrawableElement;
|
|
1892
|
+
const TRAJECTORY_ZERO_EPS = 1e-8;
|
|
1893
|
+
function nearOriginFilter(pts, eps = TRAJECTORY_ZERO_EPS) {
|
|
1894
|
+
return pts.filter((p) => Math.abs(p.x) > eps || Math.abs(p.y) > eps);
|
|
1895
|
+
}
|
|
1896
|
+
__name(nearOriginFilter, "nearOriginFilter");
|
|
1897
|
+
function normalizedT(n) {
|
|
1898
|
+
if (n <= 0) return [];
|
|
1899
|
+
if (n === 1) return [0];
|
|
1900
|
+
const t = new Array(n);
|
|
1901
|
+
for (let i = 0; i < n; i++) t[i] = i / (n - 1);
|
|
1902
|
+
return t;
|
|
1903
|
+
}
|
|
1904
|
+
__name(normalizedT, "normalizedT");
|
|
1905
|
+
function design(t, degree) {
|
|
1906
|
+
return t.map((ti) => {
|
|
1907
|
+
const row = new Array(degree + 1);
|
|
1908
|
+
let p = 1;
|
|
1909
|
+
for (let k = 0; k <= degree; k++) {
|
|
1910
|
+
row[k] = p;
|
|
1911
|
+
p *= ti;
|
|
1912
|
+
}
|
|
1913
|
+
return row;
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
__name(design, "design");
|
|
1917
|
+
function qr(A) {
|
|
1918
|
+
const n = A.length;
|
|
1919
|
+
const m = A[0].length;
|
|
1920
|
+
const Q = A.map((row) => row.slice());
|
|
1921
|
+
const R = Array.from({ length: m }, () => new Array(m).fill(0));
|
|
1922
|
+
for (let j = 0; j < m; j++) {
|
|
1923
|
+
let norm = 0;
|
|
1924
|
+
for (let i = 0; i < n; i++) norm += Q[i][j] * Q[i][j];
|
|
1925
|
+
norm = Math.sqrt(norm);
|
|
1926
|
+
R[j][j] = norm;
|
|
1927
|
+
if (norm > 1e-300) {
|
|
1928
|
+
for (let i = 0; i < n; i++) Q[i][j] /= norm;
|
|
1929
|
+
}
|
|
1930
|
+
for (let k = j + 1; k < m; k++) {
|
|
1931
|
+
let dot = 0;
|
|
1932
|
+
for (let i = 0; i < n; i++) dot += Q[i][j] * Q[i][k];
|
|
1933
|
+
R[j][k] = dot;
|
|
1934
|
+
for (let i = 0; i < n; i++) Q[i][k] -= dot * Q[i][j];
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
return { Q, R };
|
|
1938
|
+
}
|
|
1939
|
+
__name(qr, "qr");
|
|
1940
|
+
function qtY(Q, y) {
|
|
1941
|
+
const n = Q.length;
|
|
1942
|
+
const m = Q[0].length;
|
|
1943
|
+
const out = new Array(m).fill(0);
|
|
1944
|
+
for (let j = 0; j < m; j++) {
|
|
1945
|
+
let s = 0;
|
|
1946
|
+
for (let i = 0; i < n; i++) s += Q[i][j] * y[i];
|
|
1947
|
+
out[j] = s;
|
|
1948
|
+
}
|
|
1949
|
+
return out;
|
|
1950
|
+
}
|
|
1951
|
+
__name(qtY, "qtY");
|
|
1952
|
+
function backSolve(R, b) {
|
|
1953
|
+
const m = b.length;
|
|
1954
|
+
const c = new Array(m).fill(0);
|
|
1955
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
1956
|
+
let s = b[i];
|
|
1957
|
+
for (let j = i + 1; j < m; j++) s -= R[i][j] * c[j];
|
|
1958
|
+
c[i] = Math.abs(R[i][i]) > 1e-12 ? s / R[i][i] : 0;
|
|
1959
|
+
}
|
|
1960
|
+
return c;
|
|
1961
|
+
}
|
|
1962
|
+
__name(backSolve, "backSolve");
|
|
1963
|
+
function leverages(Q) {
|
|
1964
|
+
return Q.map((row) => {
|
|
1965
|
+
let s = 0;
|
|
1966
|
+
for (let j = 0; j < row.length; j++) s += row[j] * row[j];
|
|
1967
|
+
return s;
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
__name(leverages, "leverages");
|
|
1971
|
+
function fitCoeffs(t, y, degree) {
|
|
1972
|
+
const X = design(t, degree);
|
|
1973
|
+
const { Q, R } = qr(X);
|
|
1974
|
+
return backSolve(R, qtY(Q, y));
|
|
1975
|
+
}
|
|
1976
|
+
__name(fitCoeffs, "fitCoeffs");
|
|
1977
|
+
function evalPoly(coeffs, t) {
|
|
1978
|
+
let v = 0;
|
|
1979
|
+
let p = 1;
|
|
1980
|
+
for (let k = 0; k < coeffs.length; k++) {
|
|
1981
|
+
v += coeffs[k] * p;
|
|
1982
|
+
p *= t;
|
|
1983
|
+
}
|
|
1984
|
+
return v;
|
|
1985
|
+
}
|
|
1986
|
+
__name(evalPoly, "evalPoly");
|
|
1987
|
+
function loocv2d(t, x, y, degree) {
|
|
1988
|
+
const X = design(t, degree);
|
|
1989
|
+
const { Q, R } = qr(X);
|
|
1990
|
+
const h = leverages(Q);
|
|
1991
|
+
const cx = backSolve(R, qtY(Q, x));
|
|
1992
|
+
const cy = backSolve(R, qtY(Q, y));
|
|
1993
|
+
let press = 0;
|
|
1994
|
+
for (let i = 0; i < t.length; i++) {
|
|
1995
|
+
const denom = 1 - h[i];
|
|
1996
|
+
if (denom <= 0) return Infinity;
|
|
1997
|
+
const ex = x[i] - evalPoly(cx, t[i]);
|
|
1998
|
+
const ey = y[i] - evalPoly(cy, t[i]);
|
|
1999
|
+
press += (ex / denom) ** 2 + (ey / denom) ** 2;
|
|
2000
|
+
}
|
|
2001
|
+
return press;
|
|
2002
|
+
}
|
|
2003
|
+
__name(loocv2d, "loocv2d");
|
|
2004
|
+
function selectDegree(fitPts, maxDegree, mode) {
|
|
2005
|
+
const n = fitPts.length;
|
|
2006
|
+
const cap = Math.min(Math.max(1, Math.floor(maxDegree)), Math.max(1, n - 1));
|
|
2007
|
+
if (mode === "fixed" || n < 3) return cap;
|
|
2008
|
+
const t = normalizedT(n);
|
|
2009
|
+
const x = fitPts.map((p) => p.x);
|
|
2010
|
+
const y = fitPts.map((p) => p.y);
|
|
2011
|
+
let best = 1;
|
|
2012
|
+
let bestVal = Infinity;
|
|
2013
|
+
for (let deg = 1; deg <= cap; deg++) {
|
|
2014
|
+
const v = loocv2d(t, x, y, deg);
|
|
2015
|
+
if (v < bestVal) {
|
|
2016
|
+
bestVal = v;
|
|
2017
|
+
best = deg;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
return best;
|
|
2021
|
+
}
|
|
2022
|
+
__name(selectDegree, "selectDegree");
|
|
2023
|
+
function fitTrajectory(activePts, opts) {
|
|
2024
|
+
const fitPts = nearOriginFilter(activePts);
|
|
2025
|
+
if (fitPts.length < 3) return null;
|
|
2026
|
+
const selBasis = opts.selectionSet ? nearOriginFilter(opts.selectionSet) : fitPts;
|
|
2027
|
+
const basis = selBasis.length >= 3 ? selBasis : fitPts;
|
|
2028
|
+
let degree = selectDegree(basis, opts.maxDegree, opts.degreeSelection);
|
|
2029
|
+
degree = Math.min(degree, Math.max(1, fitPts.length - 1));
|
|
2030
|
+
const t = normalizedT(fitPts.length);
|
|
2031
|
+
const coeffsX = fitCoeffs(t, fitPts.map((p) => p.x), degree);
|
|
2032
|
+
const coeffsY = fitCoeffs(t, fitPts.map((p) => p.y), degree);
|
|
2033
|
+
return { degree, coeffsX, coeffsY };
|
|
2034
|
+
}
|
|
2035
|
+
__name(fitTrajectory, "fitTrajectory");
|
|
2036
|
+
function sampleCurve(fit, steps = 100) {
|
|
2037
|
+
const out = [];
|
|
2038
|
+
for (let s = 0; s <= steps; s++) {
|
|
2039
|
+
const t = s / steps;
|
|
2040
|
+
out.push({ x: evalPoly(fit.coeffsX, t), y: evalPoly(fit.coeffsY, t) });
|
|
2041
|
+
}
|
|
2042
|
+
return out;
|
|
2043
|
+
}
|
|
2044
|
+
__name(sampleCurve, "sampleCurve");
|
|
2045
|
+
const TRAJECTORY_CURVE_STEPS = 100;
|
|
2046
|
+
const _Trajectory = class _Trajectory extends PlottedObject {
|
|
2047
|
+
constructor(options = {}) {
|
|
2048
|
+
const defaultColor = options.color instanceof Color ? options.color : new Color(typeof options.color === "string" ? options.color : "#000000");
|
|
2049
|
+
super([], {
|
|
2050
|
+
ID: options.ID || `traj-${Math.random().toString(36).substring(2, 9)}`,
|
|
2051
|
+
size: options.size || 1,
|
|
2052
|
+
color: defaultColor,
|
|
2053
|
+
highlight: null,
|
|
2054
|
+
active: options.active || false,
|
|
2055
|
+
meta: options.meta || {},
|
|
2056
|
+
...options
|
|
2057
|
+
});
|
|
2058
|
+
this._typeName = "Trajectory";
|
|
2059
|
+
this.trajPoints = [];
|
|
2060
|
+
this.smooth = "none";
|
|
2061
|
+
this.maxDegree = 3;
|
|
2062
|
+
this.degreeSelection = "loocv_2d";
|
|
2063
|
+
this.selectedDegree = -1;
|
|
2064
|
+
this.polyCoeffsX = [];
|
|
2065
|
+
this.polyCoeffsY = [];
|
|
2066
|
+
this.currentTime = Infinity;
|
|
2067
|
+
this.showPoints = true;
|
|
2068
|
+
this.showTips = true;
|
|
2069
|
+
this.lineStyle = "uniform";
|
|
2070
|
+
this.strokeWidth = 2;
|
|
2071
|
+
this.strokeWidthRange = [1, 6];
|
|
2072
|
+
this.opacityRange = [0.3, 1];
|
|
2073
|
+
this.smooth = options.smooth || "none";
|
|
2074
|
+
this.maxDegree = options.maxDegree !== void 0 ? options.maxDegree : 3;
|
|
2075
|
+
this.degreeSelection = options.degreeSelection || "loocv_2d";
|
|
2076
|
+
this.polyCoeffsX = options.polyCoeffsX || [];
|
|
2077
|
+
this.polyCoeffsY = options.polyCoeffsY || [];
|
|
2078
|
+
this.currentTime = options.currentTime !== void 0 ? options.currentTime : Infinity;
|
|
2079
|
+
this.showPoints = options.showPoints !== void 0 ? options.showPoints : true;
|
|
2080
|
+
this.showTips = options.showTips !== void 0 ? options.showTips : true;
|
|
2081
|
+
this.lineStyle = options.lineStyle || "uniform";
|
|
2082
|
+
this.strokeWidth = options.strokeWidth || 2;
|
|
2083
|
+
this.strokeWidthRange = options.strokeWidthRange || [1, 6];
|
|
2084
|
+
this.opacityRange = options.opacityRange || [0.3, 1];
|
|
2085
|
+
if (options.points && Array.isArray(options.points)) {
|
|
2086
|
+
this.trajPoints = options.points.map((p) => {
|
|
2087
|
+
if (p instanceof Point) return p;
|
|
2088
|
+
const pt = new Point({ dimensions: [p.x, p.y], meta: { time: p.time, label: p.label } });
|
|
2089
|
+
pt.x = p.x;
|
|
2090
|
+
pt.y = p.y;
|
|
2091
|
+
return pt;
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
get activePoints() {
|
|
2096
|
+
if (this.currentTime === Infinity) {
|
|
2097
|
+
return this.trajPoints;
|
|
2098
|
+
}
|
|
2099
|
+
return this.trajPoints.filter((p) => {
|
|
2100
|
+
const t = p.meta && typeof p.meta.time === "number" ? p.meta.time : 0;
|
|
2101
|
+
return t <= this.currentTime;
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
get path() {
|
|
2105
|
+
return this.pathPoints.reduce((acc, pt, idx) => {
|
|
2106
|
+
return idx === 0 ? `M${pt.x},${pt.y}` : `${acc} L${pt.x},${pt.y}`;
|
|
2107
|
+
}, "");
|
|
2108
|
+
}
|
|
2109
|
+
get pathPoints() {
|
|
2110
|
+
const pts = this.activePoints;
|
|
2111
|
+
if (pts.length < 2) return [];
|
|
2112
|
+
if (this.smooth === "poly") {
|
|
2113
|
+
if (this.polyCoeffsX.length > 0 && this.polyCoeffsY.length > 0) {
|
|
2114
|
+
return sampleCurve(
|
|
2115
|
+
{ degree: this.polyCoeffsX.length - 1, coeffsX: this.polyCoeffsX, coeffsY: this.polyCoeffsY },
|
|
2116
|
+
TRAJECTORY_CURVE_STEPS
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
const fit = fitTrajectory(
|
|
2120
|
+
pts.map((p) => ({ x: p.x, y: p.y })),
|
|
2121
|
+
{
|
|
2122
|
+
maxDegree: this.maxDegree,
|
|
2123
|
+
degreeSelection: this.degreeSelection,
|
|
2124
|
+
selectionSet: this.trajPoints.map((p) => ({ x: p.x, y: p.y }))
|
|
2125
|
+
}
|
|
2126
|
+
);
|
|
2127
|
+
if (fit) {
|
|
2128
|
+
this.selectedDegree = fit.degree;
|
|
2129
|
+
return sampleCurve(fit, TRAJECTORY_CURVE_STEPS);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
return pts.map((pt) => ({ x: pt.x, y: pt.y }));
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
__name(_Trajectory, "Trajectory");
|
|
2136
|
+
let Trajectory = _Trajectory;
|
|
1871
2137
|
const zoom_level = 1;
|
|
1872
2138
|
const _Edge = class _Edge extends Line {
|
|
1873
2139
|
constructor(data, from, to, weight = 0) {
|
|
@@ -1915,7 +2181,7 @@ const _EdgeArrow = class _EdgeArrow extends PlottedObject {
|
|
|
1915
2181
|
}
|
|
1916
2182
|
get path() {
|
|
1917
2183
|
let _this = __privateGet(this, _edge);
|
|
1918
|
-
let _mid = _this.from_point, head = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.
|
|
2184
|
+
let _mid = _this.from_point, head = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.2), nock = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.35), mid = Tools.Geometry.Line.midpoint(head, nock), rot_mid = Tools.Geometry.Line.rotate(_this.line, mid), rot_nock = Tools.Geometry.Line.rotate(_this.line, nock), mid_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_mid), mid_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_mid), nock_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_nock), nock_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_nock);
|
|
1919
2185
|
let p = `M${head.join(" ")} ${mid_int0.join(" ")} ${nock_int0.join(" ")} ${mid.join(" ")} ${nock_int1.join(" ")} ${mid_int1.join(" ")}z`;
|
|
1920
2186
|
return p;
|
|
1921
2187
|
}
|
|
@@ -2200,11 +2466,22 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2200
2466
|
const extent = this.graph.__data__.extremes.x_max;
|
|
2201
2467
|
return (extent > 0 ? extent : 1) * 6e-3 * this.__data__.size;
|
|
2202
2468
|
}
|
|
2469
|
+
/** Optional halo stroke (e.g. white outline on unit points to separate
|
|
2470
|
+
* overlapping dots in dense clusters). Width scales with the dot so it holds
|
|
2471
|
+
* up at any zoom. No-op unless the object requests it via meta.stroke. */
|
|
2472
|
+
applyStroke(el) {
|
|
2473
|
+
var _a;
|
|
2474
|
+
const stroke = (_a = this.__data__.meta) == null ? void 0 : _a.stroke;
|
|
2475
|
+
if (!stroke) return;
|
|
2476
|
+
el.setAttribute("stroke", stroke);
|
|
2477
|
+
el.setAttribute("stroke-width", (this.computeRadius() * 0.22).toString());
|
|
2478
|
+
}
|
|
2203
2479
|
create() {
|
|
2204
2480
|
const tag = this.isSquare ? "rect" : "circle";
|
|
2205
2481
|
const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
2206
2482
|
el.setAttribute("data-point-id", this.__data__.ID);
|
|
2207
2483
|
el.setAttribute("fill", this.__data__.color.toString());
|
|
2484
|
+
this.applyStroke(el);
|
|
2208
2485
|
const r = this.computeRadius();
|
|
2209
2486
|
if (this.isSquare) {
|
|
2210
2487
|
el.setAttribute("x", (this.x - r).toString());
|
|
@@ -2242,6 +2519,7 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2242
2519
|
}
|
|
2243
2520
|
const r = this.computeRadius();
|
|
2244
2521
|
this.svg_element.setAttribute("fill", this.__data__.color.toString());
|
|
2522
|
+
this.applyStroke(this.svg_element);
|
|
2245
2523
|
if (this.isSquare) {
|
|
2246
2524
|
this.svg_element.setAttribute("x", (this.x - r).toString());
|
|
2247
2525
|
this.svg_element.setAttribute("y", (this.y - r).toString());
|
|
@@ -2260,6 +2538,141 @@ const _QEPoint = class _QEPoint extends Drawable {
|
|
|
2260
2538
|
};
|
|
2261
2539
|
__name(_QEPoint, "QEPoint");
|
|
2262
2540
|
let QEPoint = _QEPoint;
|
|
2541
|
+
const _QETrajectory = class _QETrajectory extends Drawable {
|
|
2542
|
+
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
2543
|
+
super(__data__, parent, graph, __children__);
|
|
2544
|
+
this.__data__ = __data__;
|
|
2545
|
+
this.parent = parent;
|
|
2546
|
+
this.graph = graph;
|
|
2547
|
+
this.__children__ = __children__;
|
|
2548
|
+
this.__name__ = "trajectory";
|
|
2549
|
+
this._path_el = null;
|
|
2550
|
+
this._segments_group = null;
|
|
2551
|
+
this._tip_el = null;
|
|
2552
|
+
this._points_group = null;
|
|
2553
|
+
this.svg_element = this.create();
|
|
2554
|
+
}
|
|
2555
|
+
// Trajectory styling is specified in display pixels, while SVG paths use
|
|
2556
|
+
// graph coordinates. Convert at render time so marks stay legible at any scale.
|
|
2557
|
+
graphUnitsForPixels(pixels) {
|
|
2558
|
+
var _a;
|
|
2559
|
+
const extent = this.graph.__data__.extremes.x_max || 1;
|
|
2560
|
+
const width = ((_a = this.graph.svg_element) == null ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
2561
|
+
return width > 0 ? pixels * (2 * extent / width) : pixels * extent * 6e-3;
|
|
2562
|
+
}
|
|
2563
|
+
create() {
|
|
2564
|
+
const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2565
|
+
group.setAttribute("class", "qe-trajectory-group");
|
|
2566
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2567
|
+
path.setAttribute("fill", "none");
|
|
2568
|
+
path.setAttribute("stroke", this.__data__.color.toString());
|
|
2569
|
+
path.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
|
|
2570
|
+
group.appendChild(path);
|
|
2571
|
+
this._path_el = path;
|
|
2572
|
+
const segmentsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2573
|
+
segmentsGroup.setAttribute("class", "qe-trajectory-segments");
|
|
2574
|
+
group.appendChild(segmentsGroup);
|
|
2575
|
+
this._segments_group = segmentsGroup;
|
|
2576
|
+
const pointsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
|
2577
|
+
pointsGroup.setAttribute("class", "qe-trajectory-points");
|
|
2578
|
+
group.appendChild(pointsGroup);
|
|
2579
|
+
this._points_group = pointsGroup;
|
|
2580
|
+
if (this.__data__.showTips) {
|
|
2581
|
+
const tip = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
2582
|
+
tip.setAttribute("fill", this.__data__.color.toString());
|
|
2583
|
+
tip.setAttribute("r", this.graphUnitsForPixels(4).toString());
|
|
2584
|
+
tip.setAttribute("stroke", "#ffffff");
|
|
2585
|
+
tip.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
|
|
2586
|
+
group.appendChild(tip);
|
|
2587
|
+
this._tip_el = tip;
|
|
2588
|
+
}
|
|
2589
|
+
this.svg_element = group;
|
|
2590
|
+
return group;
|
|
2591
|
+
}
|
|
2592
|
+
update() {
|
|
2593
|
+
if (!this.svg_element) {
|
|
2594
|
+
this.create();
|
|
2595
|
+
}
|
|
2596
|
+
if (!this.svg_element.parentElement) {
|
|
2597
|
+
this.parent.appendChild(this.svg_element);
|
|
2598
|
+
}
|
|
2599
|
+
const dStr = this.__data__.path;
|
|
2600
|
+
if (this._path_el) {
|
|
2601
|
+
this._path_el.setAttribute("d", dStr);
|
|
2602
|
+
this._path_el.setAttribute("stroke", this.__data__.color.toString());
|
|
2603
|
+
this._path_el.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
|
|
2604
|
+
this._path_el.style.display = this.__data__.lineStyle === "tapered" ? "none" : "block";
|
|
2605
|
+
}
|
|
2606
|
+
if (this._segments_group) {
|
|
2607
|
+
this._segments_group.innerHTML = "";
|
|
2608
|
+
if (this.__data__.lineStyle === "tapered") {
|
|
2609
|
+
const pathPts = this.__data__.pathPoints;
|
|
2610
|
+
const nSegments = pathPts.length - 1;
|
|
2611
|
+
const [minWidth, maxWidth] = this.__data__.strokeWidthRange;
|
|
2612
|
+
const [minOpacity, maxOpacity] = this.__data__.opacityRange;
|
|
2613
|
+
for (let i = 0; i < nSegments; i++) {
|
|
2614
|
+
const start = pathPts[i];
|
|
2615
|
+
const end = pathPts[i + 1];
|
|
2616
|
+
const progress = nSegments <= 1 ? 1 : i / (nSegments - 1);
|
|
2617
|
+
const width = minWidth + (maxWidth - minWidth) * progress;
|
|
2618
|
+
const opacity = minOpacity + (maxOpacity - minOpacity) * progress;
|
|
2619
|
+
const segment = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2620
|
+
segment.setAttribute("d", `M${start.x},${start.y} L${end.x},${end.y}`);
|
|
2621
|
+
segment.setAttribute("fill", "none");
|
|
2622
|
+
segment.setAttribute("stroke", this.__data__.color.toString());
|
|
2623
|
+
segment.setAttribute("stroke-linecap", "round");
|
|
2624
|
+
segment.setAttribute("stroke-linejoin", "round");
|
|
2625
|
+
segment.setAttribute("stroke-width", this.graphUnitsForPixels(width).toString());
|
|
2626
|
+
segment.setAttribute("opacity", opacity.toString());
|
|
2627
|
+
this._segments_group.appendChild(segment);
|
|
2628
|
+
}
|
|
2629
|
+
this._segments_group.style.display = "block";
|
|
2630
|
+
} else {
|
|
2631
|
+
this._segments_group.style.display = "none";
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
const pts = this.__data__.activePoints;
|
|
2635
|
+
if (this._points_group) {
|
|
2636
|
+
if (this.__data__.showPoints && pts.length > 0) {
|
|
2637
|
+
this._points_group.innerHTML = "";
|
|
2638
|
+
pts.forEach((pt) => {
|
|
2639
|
+
var _a;
|
|
2640
|
+
const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
2641
|
+
dot.setAttribute("cx", pt.x.toString());
|
|
2642
|
+
dot.setAttribute("cy", pt.y.toString());
|
|
2643
|
+
dot.setAttribute("r", this.graphUnitsForPixels(2.5).toString());
|
|
2644
|
+
dot.setAttribute("fill", this.__data__.color.toString());
|
|
2645
|
+
dot.setAttribute("opacity", "0.7");
|
|
2646
|
+
(_a = this._points_group) == null ? void 0 : _a.appendChild(dot);
|
|
2647
|
+
});
|
|
2648
|
+
this._points_group.style.display = "block";
|
|
2649
|
+
} else {
|
|
2650
|
+
this._points_group.style.display = "none";
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
if (this._tip_el && this.__data__.showTips) {
|
|
2654
|
+
if (pts.length > 0) {
|
|
2655
|
+
const lastPt = pts[pts.length - 1];
|
|
2656
|
+
this._tip_el.setAttribute("cx", lastPt.x.toString());
|
|
2657
|
+
this._tip_el.setAttribute("cy", lastPt.y.toString());
|
|
2658
|
+
this._tip_el.setAttribute("fill", this.__data__.color.toString());
|
|
2659
|
+
this._tip_el.setAttribute("r", this.graphUnitsForPixels(4).toString());
|
|
2660
|
+
this._tip_el.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
|
|
2661
|
+
this._tip_el.style.display = "block";
|
|
2662
|
+
} else {
|
|
2663
|
+
this._tip_el.style.display = "none";
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
draw() {
|
|
2668
|
+
if (this.svg_element && !this.svg_element.parentElement) {
|
|
2669
|
+
this.parent.appendChild(this.svg_element);
|
|
2670
|
+
}
|
|
2671
|
+
this.update();
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
__name(_QETrajectory, "QETrajectory");
|
|
2675
|
+
let QETrajectory = _QETrajectory;
|
|
2263
2676
|
const { ArrayTools } = Tools;
|
|
2264
2677
|
const _QEArray = class _QEArray extends Array {
|
|
2265
2678
|
constructor(...args) {
|
|
@@ -2319,7 +2732,8 @@ const QEElementConstructors = {
|
|
|
2319
2732
|
"Edge": QELine,
|
|
2320
2733
|
"TaperedEdge": QELine,
|
|
2321
2734
|
"TaperedWeightedMidpointEdge": QELine,
|
|
2322
|
-
"NodeEdge": QELine
|
|
2735
|
+
"NodeEdge": QELine,
|
|
2736
|
+
"Trajectory": QETrajectory
|
|
2323
2737
|
};
|
|
2324
2738
|
const _QELayer = class _QELayer {
|
|
2325
2739
|
constructor(__data__, parent, graph, __children__ = new QEArray()) {
|
|
@@ -2343,8 +2757,14 @@ const _QELayer = class _QELayer {
|
|
|
2343
2757
|
(obj) => !this.__children__.some((child) => childId(child) === obj.ID)
|
|
2344
2758
|
);
|
|
2345
2759
|
adding.forEach((obj) => {
|
|
2346
|
-
const obj_name = obj.constructor.name;
|
|
2347
|
-
|
|
2760
|
+
const obj_name = obj._typeName || obj.constructor.name;
|
|
2761
|
+
let constructor = QEElementConstructors[obj_name];
|
|
2762
|
+
if (!constructor) {
|
|
2763
|
+
if (obj instanceof Trajectory) constructor = QETrajectory;
|
|
2764
|
+
else if (obj instanceof Point) constructor = QEPoint;
|
|
2765
|
+
else constructor = QELine;
|
|
2766
|
+
}
|
|
2767
|
+
const ena_element = new constructor(obj, this.svg_element, this.graph);
|
|
2348
2768
|
this.__children__.push(ena_element);
|
|
2349
2769
|
});
|
|
2350
2770
|
updating.forEach((child) => {
|
|
@@ -2536,10 +2956,22 @@ const _QEEdgesElement = class _QEEdgesElement extends HTMLElement {
|
|
|
2536
2956
|
__name(_QEEdgesElement, "QEEdgesElement");
|
|
2537
2957
|
_QEEdgesElement.tagName = "qe-edges";
|
|
2538
2958
|
let QEEdgesElement = _QEEdgesElement;
|
|
2959
|
+
const _QETrajectoriesElement = class _QETrajectoriesElement extends HTMLElement {
|
|
2960
|
+
};
|
|
2961
|
+
__name(_QETrajectoriesElement, "QETrajectoriesElement");
|
|
2962
|
+
_QETrajectoriesElement.tagName = "qe-trajectories";
|
|
2963
|
+
let QETrajectoriesElement = _QETrajectoriesElement;
|
|
2964
|
+
const _QEAxesElement = class _QEAxesElement extends HTMLElement {
|
|
2965
|
+
};
|
|
2966
|
+
__name(_QEAxesElement, "QEAxesElement");
|
|
2967
|
+
_QEAxesElement.tagName = "qe-axes";
|
|
2968
|
+
let QEAxesElement = _QEAxesElement;
|
|
2539
2969
|
customElements.define(QENodesElement.tagName, QENodesElement);
|
|
2540
2970
|
customElements.define(QEMeansElement.tagName, QEMeansElement);
|
|
2541
2971
|
customElements.define(QEPointsElement.tagName, QEPointsElement);
|
|
2542
2972
|
customElements.define(QEEdgesElement.tagName, QEEdgesElement);
|
|
2973
|
+
customElements.define(QETrajectoriesElement.tagName, QETrajectoriesElement);
|
|
2974
|
+
customElements.define(QEAxesElement.tagName, QEAxesElement);
|
|
2543
2975
|
const GROUP_PALETTE = [
|
|
2544
2976
|
"#4477AA",
|
|
2545
2977
|
"#EE6677",
|
|
@@ -2613,6 +3045,10 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2613
3045
|
this._nodeLabels = /* @__PURE__ */ new Map();
|
|
2614
3046
|
this._meanLabels = /* @__PURE__ */ new Map();
|
|
2615
3047
|
this._pointLabels = /* @__PURE__ */ new Map();
|
|
3048
|
+
this._showAxes = false;
|
|
3049
|
+
this._axisXText = null;
|
|
3050
|
+
this._axisYText = null;
|
|
3051
|
+
this._axisLabelEls = /* @__PURE__ */ new Map();
|
|
2616
3052
|
this.graph = this;
|
|
2617
3053
|
this.parent = null;
|
|
2618
3054
|
this.__data__ = new Graph(DefaultGraphOptions);
|
|
@@ -2620,7 +3056,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2620
3056
|
this.svg_element = this.create();
|
|
2621
3057
|
console.log("Created ena-graph svg element: ", this.svg_element);
|
|
2622
3058
|
document.addEventListener("model-updated", (e) => {
|
|
2623
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
3059
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
2624
3060
|
if (this._vizena && e.target !== this._vizena) return;
|
|
2625
3061
|
const model = e.detail;
|
|
2626
3062
|
if (!model) return;
|
|
@@ -2630,6 +3066,9 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2630
3066
|
const nodeIdCol = model.node_id_col;
|
|
2631
3067
|
const groupColName = model.group_col;
|
|
2632
3068
|
const config = this._readLayerConfig();
|
|
3069
|
+
this._showAxes = config.showAxes;
|
|
3070
|
+
this._axisXText = config.axisXLabel === null ? null : config.axisXLabel || String((dims == null ? void 0 : dims[0]) ?? "x");
|
|
3071
|
+
this._axisYText = config.axisYLabel === null ? null : config.axisYLabel || String((dims == null ? void 0 : dims[1]) ?? "y");
|
|
2633
3072
|
const groupColorMap = {};
|
|
2634
3073
|
if (groupColName && ((_a = model.points) == null ? void 0 : _a.names)) {
|
|
2635
3074
|
const groupCol = model.points.col(groupColName);
|
|
@@ -2663,7 +3102,9 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2663
3102
|
}, "getExtent");
|
|
2664
3103
|
const maxNodeExtent = ((_c = model.nodes) == null ? void 0 : _c.rows) ? getExtent(model.nodes.rows()) : 0;
|
|
2665
3104
|
const maxPointExtent = ((_d = model.points) == null ? void 0 : _d.rows) ? getExtent(model.points.rows()) : 0;
|
|
2666
|
-
const
|
|
3105
|
+
const maxTrajExtent = ((_e = model.trajectories) == null ? void 0 : _e.rows) ? getExtent(model.trajectories.rows()) : 0;
|
|
3106
|
+
const effectivePointExtent = maxPointExtent > 0 ? maxPointExtent : maxTrajExtent;
|
|
3107
|
+
const pointScale = scalePointsEnabled && effectivePointExtent > 0 && maxNodeExtent > 0 ? maxNodeExtent / effectivePointExtent : 1;
|
|
2667
3108
|
const labelNodes = config.labelNodes;
|
|
2668
3109
|
const labelMeans = config.labelMeans;
|
|
2669
3110
|
const labelPoints = config.labelPoints;
|
|
@@ -2686,7 +3127,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2686
3127
|
}, "rowToPoint");
|
|
2687
3128
|
const groupSums = {};
|
|
2688
3129
|
const showUnitPoints = config.showPoints;
|
|
2689
|
-
if ((
|
|
3130
|
+
if ((_f = model.points) == null ? void 0 : _f.rows) {
|
|
2690
3131
|
const pointsLayer = this.__children__.search("__children__", "points");
|
|
2691
3132
|
if (pointsLayer) {
|
|
2692
3133
|
model.points.rows().forEach((row) => {
|
|
@@ -2695,6 +3136,8 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2695
3136
|
const colorStr = group ? groupColorMap[group] ?? "#888888" : "#888888";
|
|
2696
3137
|
if (showUnitPoints) {
|
|
2697
3138
|
const pt = rowToPoint(row, unitIdCol, colorStr, pointScale);
|
|
3139
|
+
pt.size = 2.5;
|
|
3140
|
+
pt.meta = { ...pt.meta ?? {}, stroke: "#ffffff" };
|
|
2698
3141
|
this.__data__.points.set(pt.ID, pt);
|
|
2699
3142
|
pointsLayer.__data__.objects.set(pt.ID, pt);
|
|
2700
3143
|
}
|
|
@@ -2712,7 +3155,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2712
3155
|
groupSums[group].n += 1;
|
|
2713
3156
|
}
|
|
2714
3157
|
});
|
|
2715
|
-
const hasGroupsFrame = !!(((
|
|
3158
|
+
const hasGroupsFrame = !!(((_g = model.groups) == null ? void 0 : _g.rows) && model.groups.rows().length > 0);
|
|
2716
3159
|
const activeGroupRows = config.showMeans && hasGroupsFrame ? model.groups.rows().filter((r) => {
|
|
2717
3160
|
const g = r.rowName ?? String(r[0]);
|
|
2718
3161
|
return activeMeanGroups.size === 0 || activeMeanGroups.has(g);
|
|
@@ -2798,7 +3241,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2798
3241
|
this._ciRects.delete(g);
|
|
2799
3242
|
}
|
|
2800
3243
|
});
|
|
2801
|
-
if ((
|
|
3244
|
+
if ((_h = model.confidence) == null ? void 0 : _h.rows) {
|
|
2802
3245
|
model.confidence.rows().forEach((row) => {
|
|
2803
3246
|
const groupIdx = row.names.indexOf(groupColName ?? "group");
|
|
2804
3247
|
const group = groupIdx >= 0 ? String(row[groupIdx]) : row.rowName ?? null;
|
|
@@ -2858,7 +3301,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2858
3301
|
});
|
|
2859
3302
|
}
|
|
2860
3303
|
}
|
|
2861
|
-
if ((
|
|
3304
|
+
if ((_i = model.outlier) == null ? void 0 : _i.rows) {
|
|
2862
3305
|
this._outlierRects.forEach((el, g) => {
|
|
2863
3306
|
if (!groupColorMap[g]) {
|
|
2864
3307
|
el.remove();
|
|
@@ -2947,20 +3390,73 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
2947
3390
|
pointsLayer.__data__.__state__.setVariable("layer_updated", pointsLayer.__data__);
|
|
2948
3391
|
}
|
|
2949
3392
|
}
|
|
3393
|
+
const trajectoriesLayer = this.__children__.search("__children__", "trajectories");
|
|
3394
|
+
const trajectoriesEnabled = config.showTrajectories;
|
|
3395
|
+
if (trajectoriesEnabled && trajectoriesLayer) {
|
|
3396
|
+
trajectoriesLayer.__data__.objects.clear();
|
|
3397
|
+
this.__data__.trajectories.clear();
|
|
3398
|
+
const trajSource = ((_j = model.trajectories) == null ? void 0 : _j.rows) ? model.trajectories : model.points;
|
|
3399
|
+
if (trajSource == null ? void 0 : trajSource.rows) {
|
|
3400
|
+
const unitRows = {};
|
|
3401
|
+
const selectedUnits = config.trajectoryUnits ? config.trajectoryUnits.split(",").map((s) => s.trim()) : null;
|
|
3402
|
+
trajSource.rows().forEach((row) => {
|
|
3403
|
+
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;
|
|
3404
|
+
const unit = uIdx >= 0 ? String(row[uIdx]) : row.rowName || "unit-1";
|
|
3405
|
+
if (selectedUnits && !selectedUnits.includes(unit)) return;
|
|
3406
|
+
const xIdx = row.names.indexOf(dims[0]);
|
|
3407
|
+
const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
|
|
3408
|
+
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;
|
|
3409
|
+
const time = tIdx >= 0 ? Number(row[tIdx]) : unitRows[unit] ? unitRows[unit].length + 1 : 1;
|
|
3410
|
+
if (xIdx >= 0 && yIdx >= 0) {
|
|
3411
|
+
if (!unitRows[unit]) unitRows[unit] = [];
|
|
3412
|
+
unitRows[unit].push({
|
|
3413
|
+
x: Number(row[xIdx]) * pointScale,
|
|
3414
|
+
y: -Number(row[yIdx]) * pointScale,
|
|
3415
|
+
time
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
});
|
|
3419
|
+
const unitKeys = Object.keys(unitRows);
|
|
3420
|
+
unitKeys.forEach((unitKey, uIdx) => {
|
|
3421
|
+
const rawPts = unitRows[unitKey];
|
|
3422
|
+
if (rawPts.length === 0) return;
|
|
3423
|
+
const colorStr = GROUP_PALETTE[uIdx % GROUP_PALETTE.length];
|
|
3424
|
+
const trajObj = new Trajectory({
|
|
3425
|
+
ID: `traj-${unitKey}`,
|
|
3426
|
+
unitId: unitKey,
|
|
3427
|
+
color: new Color(colorStr),
|
|
3428
|
+
lineStyle: config.trajectoryLineStyle,
|
|
3429
|
+
strokeWidth: config.trajectoryStrokeWidth,
|
|
3430
|
+
strokeWidthRange: config.trajectoryStrokeWidthRange,
|
|
3431
|
+
opacityRange: config.trajectoryOpacityRange,
|
|
3432
|
+
smooth: config.trajectorySmooth,
|
|
3433
|
+
maxDegree: config.trajectoryMaxDegree,
|
|
3434
|
+
degreeSelection: config.trajectoryDegreeSelection,
|
|
3435
|
+
currentTime: config.trajectoryPlayhead ?? Infinity,
|
|
3436
|
+
showPoints: config.trajectoryShowPoints,
|
|
3437
|
+
showTips: config.trajectoryShowTips,
|
|
3438
|
+
points: rawPts.map((p) => ({ x: p.x, y: p.y, time: p.time }))
|
|
3439
|
+
});
|
|
3440
|
+
this.__data__.trajectories.set(unitKey, trajObj);
|
|
3441
|
+
trajectoriesLayer.__data__.objects.set(trajObj.ID, trajObj);
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3444
|
+
trajectoriesLayer.__data__.__state__.setVariable("layer_updated", trajectoriesLayer.__data__);
|
|
3445
|
+
}
|
|
2950
3446
|
const networkLayer = this.__children__.search("__children__", "network");
|
|
2951
3447
|
const edgeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edges");
|
|
2952
3448
|
const nodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "nodes");
|
|
2953
3449
|
const edgenodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edgenodes");
|
|
2954
3450
|
const edgesEnabled = config.showEdges;
|
|
2955
|
-
if (((
|
|
3451
|
+
if (((_k = model.nodes) == null ? void 0 : _k.rows) && nodeLayer) {
|
|
2956
3452
|
model.nodes.rows().forEach((row) => {
|
|
2957
3453
|
const pt = rowToPoint(row, nodeIdCol, "#444444");
|
|
2958
3454
|
pt.size = 2;
|
|
2959
3455
|
nodeLayer.__data__.objects.set(pt.ID, pt);
|
|
2960
3456
|
});
|
|
2961
|
-
if (edgesEnabled && edgeLayer && ((
|
|
3457
|
+
if (edgesEnabled && edgeLayer && ((_l = model.edges) == null ? void 0 : _l.rows)) {
|
|
2962
3458
|
const unitGroupMap = {};
|
|
2963
|
-
if (groupColName && ((
|
|
3459
|
+
if (groupColName && ((_m = model.points) == null ? void 0 : _m.rows)) {
|
|
2964
3460
|
model.points.rows().forEach((row) => {
|
|
2965
3461
|
const qIdx = row.names.indexOf(unitIdCol);
|
|
2966
3462
|
const gIdx = row.names.indexOf(groupColName);
|
|
@@ -3086,7 +3582,6 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3086
3582
|
totalWeightByPair[key] = (totalWeightByPair[key] ?? 0) + weight;
|
|
3087
3583
|
});
|
|
3088
3584
|
}
|
|
3089
|
-
((_l = this.__data__.extremes) == null ? void 0 : _l.x_max) ?? 1;
|
|
3090
3585
|
drawEdges.forEach(({ col, pair, weight, color }) => {
|
|
3091
3586
|
const fromNode = nodeLayer.__data__.objects.get(pair[0]);
|
|
3092
3587
|
const toNode = nodeLayer.__data__.objects.get(pair[1]);
|
|
@@ -3208,6 +3703,8 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3208
3703
|
const meansEl = this.querySelector("qe-means");
|
|
3209
3704
|
const pointsEl = this.querySelector("qe-points");
|
|
3210
3705
|
const edgesEl = this.querySelector("qe-edges");
|
|
3706
|
+
const trajectoriesEl = this.querySelector("qe-trajectories");
|
|
3707
|
+
const axesEl = this.querySelector("qe-axes");
|
|
3211
3708
|
const labelsShorthand = this.getAttribute("labels");
|
|
3212
3709
|
return {
|
|
3213
3710
|
// Nodes — always rendered (coordinate anchors); <qe-nodes> controls labels.
|
|
@@ -3235,9 +3732,35 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3235
3732
|
edgeGroup: (edgesEl == null ? void 0 : edgesEl.getAttribute("group")) ?? null,
|
|
3236
3733
|
edgeUnit: (edgesEl == null ? void 0 : edgesEl.getAttribute("unit")) ?? null,
|
|
3237
3734
|
edgeCompare: (edgesEl == null ? void 0 : edgesEl.getAttribute("compare")) ?? null,
|
|
3238
|
-
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null
|
|
3735
|
+
edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null,
|
|
3736
|
+
// Trajectories — presence of <qe-trajectories> = draw cognitive trajectories
|
|
3737
|
+
showTrajectories: !!trajectoriesEl,
|
|
3738
|
+
trajectoryUnits: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("units")) ?? null,
|
|
3739
|
+
trajectorySmooth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("smooth")) ?? "none",
|
|
3740
|
+
trajectoryMaxDegree: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("max-degree")) ? parseInt(trajectoriesEl.getAttribute("max-degree"), 10) : 3,
|
|
3741
|
+
trajectoryDegreeSelection: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("degree-selection")) ?? "loocv_2d",
|
|
3742
|
+
trajectoryShowPoints: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("points")) !== "false",
|
|
3743
|
+
trajectoryShowTips: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("tips")) !== "false",
|
|
3744
|
+
trajectoryPlayhead: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("playhead")) ? parseFloat(trajectoriesEl.getAttribute("playhead")) : null,
|
|
3745
|
+
trajectoryLineStyle: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("line-style")) ?? "uniform",
|
|
3746
|
+
trajectoryStrokeWidth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width")) ? parseFloat(trajectoriesEl.getAttribute("stroke-width")) : 2,
|
|
3747
|
+
trajectoryStrokeWidthRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width-range"), [1, 6]),
|
|
3748
|
+
trajectoryOpacityRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("opacity-range"), [0.3, 1]),
|
|
3749
|
+
// Axes — presence of <qe-axes> = draw axis lines. The x / y attributes add
|
|
3750
|
+
// a per-axis label: a value is used verbatim, an empty attribute falls back
|
|
3751
|
+
// to the dimension name (resolved later, where dims are known). null = no
|
|
3752
|
+
// label for that axis.
|
|
3753
|
+
showAxes: !!axesEl,
|
|
3754
|
+
axisXLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("x")) ? axesEl.getAttribute("x") ?? "" : null,
|
|
3755
|
+
axisYLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("y")) ? axesEl.getAttribute("y") ?? "" : null
|
|
3239
3756
|
};
|
|
3240
3757
|
}
|
|
3758
|
+
_parseNumberPair(value, fallback) {
|
|
3759
|
+
if (!value) return fallback;
|
|
3760
|
+
const parts = value.split(",").map((part) => Number(part.trim()));
|
|
3761
|
+
if (parts.length < 2 || !parts.every(Number.isFinite)) return fallback;
|
|
3762
|
+
return [parts[0], parts[1]];
|
|
3763
|
+
}
|
|
3241
3764
|
attributeChangedCallback(_name2, oldValue, newValue) {
|
|
3242
3765
|
if (oldValue === newValue) return;
|
|
3243
3766
|
this._scheduleRerender();
|
|
@@ -3246,6 +3769,7 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3246
3769
|
var _a;
|
|
3247
3770
|
const w = this.getAttribute("width");
|
|
3248
3771
|
const h = this.getAttribute("height");
|
|
3772
|
+
this.style.display = "block";
|
|
3249
3773
|
if (w) this.style.width = isNaN(Number(w)) ? w : `${w}px`;
|
|
3250
3774
|
if (h) this.style.height = isNaN(Number(h)) ? h : `${h}px`;
|
|
3251
3775
|
this.appendChild(this.svg_element);
|
|
@@ -3266,7 +3790,19 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3266
3790
|
"labels",
|
|
3267
3791
|
"label-nodes",
|
|
3268
3792
|
"label-means",
|
|
3269
|
-
"label-points"
|
|
3793
|
+
"label-points",
|
|
3794
|
+
"units",
|
|
3795
|
+
"smooth",
|
|
3796
|
+
"degree",
|
|
3797
|
+
"points",
|
|
3798
|
+
"tips",
|
|
3799
|
+
"playhead",
|
|
3800
|
+
"line-style",
|
|
3801
|
+
"stroke-width",
|
|
3802
|
+
"stroke-width-range",
|
|
3803
|
+
"opacity-range",
|
|
3804
|
+
"x",
|
|
3805
|
+
"y"
|
|
3270
3806
|
]
|
|
3271
3807
|
});
|
|
3272
3808
|
const forId = this.getAttribute("for");
|
|
@@ -3293,6 +3829,10 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3293
3829
|
create() {
|
|
3294
3830
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3295
3831
|
svg.classList.add("graph");
|
|
3832
|
+
svg.style.display = "block";
|
|
3833
|
+
svg.style.width = "100%";
|
|
3834
|
+
svg.style.height = "100%";
|
|
3835
|
+
svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
3296
3836
|
svg.setAttribute("viewBox", this.__data__.extremes.as_viewBox());
|
|
3297
3837
|
svg.setAttribute("transform", this.__data__.transform.toString());
|
|
3298
3838
|
svg.setAttribute("role", "figure");
|
|
@@ -3419,6 +3959,56 @@ const _QEGraphElement = class _QEGraphElement extends DrawableElement {
|
|
|
3419
3959
|
this.update_layers();
|
|
3420
3960
|
this.update_labels();
|
|
3421
3961
|
this.update_extremes();
|
|
3962
|
+
this.update_axes();
|
|
3963
|
+
}
|
|
3964
|
+
/**
|
|
3965
|
+
* Axes (<qe-axes>): toggle the axis-line cross by presence, and draw/position
|
|
3966
|
+
* per-axis labels at the positive ends using the current extent. Labels are
|
|
3967
|
+
* cached SVG <text> nodes in the labels group; null text removes them.
|
|
3968
|
+
*/
|
|
3969
|
+
update_axes() {
|
|
3970
|
+
const axesLayer = this.__children__.search("__children__", "axes");
|
|
3971
|
+
if (axesLayer == null ? void 0 : axesLayer.svg_element) {
|
|
3972
|
+
axesLayer.svg_element.style.display = this._showAxes ? "" : "none";
|
|
3973
|
+
}
|
|
3974
|
+
const parent = this._labelsGroup;
|
|
3975
|
+
const extent = this.__data__.extremes.x_max;
|
|
3976
|
+
if (!parent || extent <= 0) return;
|
|
3977
|
+
const fontSize = extent * 0.06;
|
|
3978
|
+
const gap = fontSize * 0.6;
|
|
3979
|
+
const place = /* @__PURE__ */ __name((axis, text) => {
|
|
3980
|
+
let el = this._axisLabelEls.get(axis);
|
|
3981
|
+
if (!this._showAxes || text == null) {
|
|
3982
|
+
if (el) {
|
|
3983
|
+
el.remove();
|
|
3984
|
+
this._axisLabelEls.delete(axis);
|
|
3985
|
+
}
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
if (!el) {
|
|
3989
|
+
el = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
3990
|
+
el.setAttribute("data-axis-label", axis);
|
|
3991
|
+
el.setAttribute("pointer-events", "none");
|
|
3992
|
+
parent.appendChild(el);
|
|
3993
|
+
this._axisLabelEls.set(axis, el);
|
|
3994
|
+
}
|
|
3995
|
+
el.textContent = text;
|
|
3996
|
+
el.setAttribute("font-size", fontSize.toString());
|
|
3997
|
+
el.setAttribute("font-family", "system-ui, sans-serif");
|
|
3998
|
+
el.setAttribute("font-weight", "600");
|
|
3999
|
+
el.setAttribute("fill", "#556");
|
|
4000
|
+
if (axis === "x") {
|
|
4001
|
+
el.setAttribute("x", (extent + gap).toString());
|
|
4002
|
+
el.setAttribute("y", (fontSize * 0.35).toString());
|
|
4003
|
+
el.setAttribute("text-anchor", "start");
|
|
4004
|
+
} else {
|
|
4005
|
+
el.setAttribute("x", "0");
|
|
4006
|
+
el.setAttribute("y", (-extent - gap).toString());
|
|
4007
|
+
el.setAttribute("text-anchor", "middle");
|
|
4008
|
+
}
|
|
4009
|
+
}, "place");
|
|
4010
|
+
place("x", this._axisXText);
|
|
4011
|
+
place("y", this._axisYText);
|
|
3422
4012
|
}
|
|
3423
4013
|
update_labels() {
|
|
3424
4014
|
this._nodeLabels.forEach((lbl) => lbl.update());
|
|
@@ -3492,6 +4082,7 @@ export {
|
|
|
3492
4082
|
DataFrameRow,
|
|
3493
4083
|
Edge,
|
|
3494
4084
|
Graph,
|
|
4085
|
+
QEAxesElement,
|
|
3495
4086
|
QEEdgesElement,
|
|
3496
4087
|
QEGraphElement,
|
|
3497
4088
|
QELayer,
|
|
@@ -3500,8 +4091,16 @@ export {
|
|
|
3500
4091
|
QENodesElement,
|
|
3501
4092
|
QEPoint,
|
|
3502
4093
|
QEPointsElement,
|
|
4094
|
+
QETrajectoriesElement,
|
|
4095
|
+
QETrajectory,
|
|
3503
4096
|
QEVisualElement,
|
|
4097
|
+
TRAJECTORY_ZERO_EPS,
|
|
4098
|
+
Trajectory,
|
|
3504
4099
|
WeightedColor,
|
|
4100
|
+
fitTrajectory,
|
|
4101
|
+
nearOriginFilter,
|
|
4102
|
+
sampleCurve,
|
|
4103
|
+
selectDegree,
|
|
3505
4104
|
vizENA
|
|
3506
4105
|
};
|
|
3507
4106
|
//# sourceMappingURL=qeviz.es.js.map
|