@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/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: inline-block; }
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);
@@ -816,7 +829,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
816
829
  __name(slopeIntercept, "slopeIntercept");
817
830
  Line2.slopeIntercept = slopeIntercept;
818
831
  function point_along(line, distance = 0) {
819
- let len = Tools2.Geometry.Line.length(line[0], line[1]), ratio = distance / len, Xt = (1 - ratio) * line[0][0] + ratio * line[1][0], Yt = (1 - ratio) * line[0][1] + ratio * line[1][1];
832
+ const ratio = distance;
833
+ const Xt = (1 - ratio) * line[0][0] + ratio * line[1][0];
834
+ const Yt = (1 - ratio) * line[0][1] + ratio * line[1][1];
820
835
  return [Xt, Yt];
821
836
  }
822
837
  __name(point_along, "point_along");
@@ -1663,6 +1678,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
1663
1678
  this.points = /* @__PURE__ */ new Map();
1664
1679
  this.nodes = /* @__PURE__ */ new Map();
1665
1680
  this.edges = /* @__PURE__ */ new Map();
1681
+ this.trajectories = /* @__PURE__ */ new Map();
1666
1682
  this.labels = /* @__PURE__ */ new Map();
1667
1683
  this.transform = new Transform();
1668
1684
  this.scale_x = 1;
@@ -1705,6 +1721,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
1705
1721
  { name: "edgenodes", layers: [], tabindex: 0 }
1706
1722
  ]
1707
1723
  },
1724
+ { name: "trajectories", layers: [], tabindex: 0 },
1708
1725
  { name: "points", layers: [], tabindex: 0 },
1709
1726
  { name: "labels", layers: [], tabindex: 0 }
1710
1727
  ] }
@@ -1830,9 +1847,13 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
1830
1847
  if (search_layers.length === 0) {
1831
1848
  this.extremes = new Extremes(1, 1);
1832
1849
  } else {
1833
- const found_max = Math.max(...search_layers.map((l) => Math.max(...[...l.objects.values()].map((o) => Math.max(Math.abs(o.x), Math.abs(o.y))))).filter((a) => Math.abs(a) !== Infinity)) * 1.5;
1834
- console.log("Found max for extremes:", found_max);
1835
- this.extremes = new Extremes(Math.ceil(found_max), Math.ceil(found_max));
1850
+ const content_max = Math.max(
1851
+ ...search_layers.map((l) => Math.max(
1852
+ ...[...l.objects.values()].map((o) => Math.max(Math.abs(o.x), Math.abs(o.y)))
1853
+ )).filter((a) => Math.abs(a) !== Infinity)
1854
+ );
1855
+ const found_max = (Number.isFinite(content_max) && content_max > 0 ? content_max : 1) * 1.3;
1856
+ this.extremes = new Extremes(found_max, found_max);
1836
1857
  }
1837
1858
  }
1838
1859
  base_point_size(scale = 8) {
@@ -1872,6 +1893,251 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
1872
1893
  }
1873
1894
  `;
1874
1895
  let DrawableElement = _DrawableElement;
1896
+ const TRAJECTORY_ZERO_EPS = 1e-8;
1897
+ function nearOriginFilter(pts, eps = TRAJECTORY_ZERO_EPS) {
1898
+ return pts.filter((p) => Math.abs(p.x) > eps || Math.abs(p.y) > eps);
1899
+ }
1900
+ __name(nearOriginFilter, "nearOriginFilter");
1901
+ function normalizedT(n) {
1902
+ if (n <= 0) return [];
1903
+ if (n === 1) return [0];
1904
+ const t = new Array(n);
1905
+ for (let i = 0; i < n; i++) t[i] = i / (n - 1);
1906
+ return t;
1907
+ }
1908
+ __name(normalizedT, "normalizedT");
1909
+ function design(t, degree) {
1910
+ return t.map((ti) => {
1911
+ const row = new Array(degree + 1);
1912
+ let p = 1;
1913
+ for (let k = 0; k <= degree; k++) {
1914
+ row[k] = p;
1915
+ p *= ti;
1916
+ }
1917
+ return row;
1918
+ });
1919
+ }
1920
+ __name(design, "design");
1921
+ function qr(A) {
1922
+ const n = A.length;
1923
+ const m = A[0].length;
1924
+ const Q = A.map((row) => row.slice());
1925
+ const R = Array.from({ length: m }, () => new Array(m).fill(0));
1926
+ for (let j = 0; j < m; j++) {
1927
+ let norm = 0;
1928
+ for (let i = 0; i < n; i++) norm += Q[i][j] * Q[i][j];
1929
+ norm = Math.sqrt(norm);
1930
+ R[j][j] = norm;
1931
+ if (norm > 1e-300) {
1932
+ for (let i = 0; i < n; i++) Q[i][j] /= norm;
1933
+ }
1934
+ for (let k = j + 1; k < m; k++) {
1935
+ let dot = 0;
1936
+ for (let i = 0; i < n; i++) dot += Q[i][j] * Q[i][k];
1937
+ R[j][k] = dot;
1938
+ for (let i = 0; i < n; i++) Q[i][k] -= dot * Q[i][j];
1939
+ }
1940
+ }
1941
+ return { Q, R };
1942
+ }
1943
+ __name(qr, "qr");
1944
+ function qtY(Q, y) {
1945
+ const n = Q.length;
1946
+ const m = Q[0].length;
1947
+ const out = new Array(m).fill(0);
1948
+ for (let j = 0; j < m; j++) {
1949
+ let s = 0;
1950
+ for (let i = 0; i < n; i++) s += Q[i][j] * y[i];
1951
+ out[j] = s;
1952
+ }
1953
+ return out;
1954
+ }
1955
+ __name(qtY, "qtY");
1956
+ function backSolve(R, b) {
1957
+ const m = b.length;
1958
+ const c = new Array(m).fill(0);
1959
+ for (let i = m - 1; i >= 0; i--) {
1960
+ let s = b[i];
1961
+ for (let j = i + 1; j < m; j++) s -= R[i][j] * c[j];
1962
+ c[i] = Math.abs(R[i][i]) > 1e-12 ? s / R[i][i] : 0;
1963
+ }
1964
+ return c;
1965
+ }
1966
+ __name(backSolve, "backSolve");
1967
+ function leverages(Q) {
1968
+ return Q.map((row) => {
1969
+ let s = 0;
1970
+ for (let j = 0; j < row.length; j++) s += row[j] * row[j];
1971
+ return s;
1972
+ });
1973
+ }
1974
+ __name(leverages, "leverages");
1975
+ function fitCoeffs(t, y, degree) {
1976
+ const X = design(t, degree);
1977
+ const { Q, R } = qr(X);
1978
+ return backSolve(R, qtY(Q, y));
1979
+ }
1980
+ __name(fitCoeffs, "fitCoeffs");
1981
+ function evalPoly(coeffs, t) {
1982
+ let v = 0;
1983
+ let p = 1;
1984
+ for (let k = 0; k < coeffs.length; k++) {
1985
+ v += coeffs[k] * p;
1986
+ p *= t;
1987
+ }
1988
+ return v;
1989
+ }
1990
+ __name(evalPoly, "evalPoly");
1991
+ function loocv2d(t, x, y, degree) {
1992
+ const X = design(t, degree);
1993
+ const { Q, R } = qr(X);
1994
+ const h = leverages(Q);
1995
+ const cx = backSolve(R, qtY(Q, x));
1996
+ const cy = backSolve(R, qtY(Q, y));
1997
+ let press = 0;
1998
+ for (let i = 0; i < t.length; i++) {
1999
+ const denom = 1 - h[i];
2000
+ if (denom <= 0) return Infinity;
2001
+ const ex = x[i] - evalPoly(cx, t[i]);
2002
+ const ey = y[i] - evalPoly(cy, t[i]);
2003
+ press += (ex / denom) ** 2 + (ey / denom) ** 2;
2004
+ }
2005
+ return press;
2006
+ }
2007
+ __name(loocv2d, "loocv2d");
2008
+ function selectDegree(fitPts, maxDegree, mode) {
2009
+ const n = fitPts.length;
2010
+ const cap = Math.min(Math.max(1, Math.floor(maxDegree)), Math.max(1, n - 1));
2011
+ if (mode === "fixed" || n < 3) return cap;
2012
+ const t = normalizedT(n);
2013
+ const x = fitPts.map((p) => p.x);
2014
+ const y = fitPts.map((p) => p.y);
2015
+ let best = 1;
2016
+ let bestVal = Infinity;
2017
+ for (let deg = 1; deg <= cap; deg++) {
2018
+ const v = loocv2d(t, x, y, deg);
2019
+ if (v < bestVal) {
2020
+ bestVal = v;
2021
+ best = deg;
2022
+ }
2023
+ }
2024
+ return best;
2025
+ }
2026
+ __name(selectDegree, "selectDegree");
2027
+ function fitTrajectory(activePts, opts) {
2028
+ const fitPts = nearOriginFilter(activePts);
2029
+ if (fitPts.length < 3) return null;
2030
+ const selBasis = opts.selectionSet ? nearOriginFilter(opts.selectionSet) : fitPts;
2031
+ const basis = selBasis.length >= 3 ? selBasis : fitPts;
2032
+ let degree = selectDegree(basis, opts.maxDegree, opts.degreeSelection);
2033
+ degree = Math.min(degree, Math.max(1, fitPts.length - 1));
2034
+ const t = normalizedT(fitPts.length);
2035
+ const coeffsX = fitCoeffs(t, fitPts.map((p) => p.x), degree);
2036
+ const coeffsY = fitCoeffs(t, fitPts.map((p) => p.y), degree);
2037
+ return { degree, coeffsX, coeffsY };
2038
+ }
2039
+ __name(fitTrajectory, "fitTrajectory");
2040
+ function sampleCurve(fit, steps = 100) {
2041
+ const out = [];
2042
+ for (let s = 0; s <= steps; s++) {
2043
+ const t = s / steps;
2044
+ out.push({ x: evalPoly(fit.coeffsX, t), y: evalPoly(fit.coeffsY, t) });
2045
+ }
2046
+ return out;
2047
+ }
2048
+ __name(sampleCurve, "sampleCurve");
2049
+ const TRAJECTORY_CURVE_STEPS = 100;
2050
+ const _Trajectory = class _Trajectory extends PlottedObject {
2051
+ constructor(options = {}) {
2052
+ const defaultColor = options.color instanceof Color ? options.color : new Color(typeof options.color === "string" ? options.color : "#000000");
2053
+ super([], {
2054
+ ID: options.ID || `traj-${Math.random().toString(36).substring(2, 9)}`,
2055
+ size: options.size || 1,
2056
+ color: defaultColor,
2057
+ highlight: null,
2058
+ active: options.active || false,
2059
+ meta: options.meta || {},
2060
+ ...options
2061
+ });
2062
+ this._typeName = "Trajectory";
2063
+ this.trajPoints = [];
2064
+ this.smooth = "none";
2065
+ this.maxDegree = 3;
2066
+ this.degreeSelection = "loocv_2d";
2067
+ this.selectedDegree = -1;
2068
+ this.polyCoeffsX = [];
2069
+ this.polyCoeffsY = [];
2070
+ this.currentTime = Infinity;
2071
+ this.showPoints = true;
2072
+ this.showTips = true;
2073
+ this.lineStyle = "uniform";
2074
+ this.strokeWidth = 2;
2075
+ this.strokeWidthRange = [1, 6];
2076
+ this.opacityRange = [0.3, 1];
2077
+ this.smooth = options.smooth || "none";
2078
+ this.maxDegree = options.maxDegree !== void 0 ? options.maxDegree : 3;
2079
+ this.degreeSelection = options.degreeSelection || "loocv_2d";
2080
+ this.polyCoeffsX = options.polyCoeffsX || [];
2081
+ this.polyCoeffsY = options.polyCoeffsY || [];
2082
+ this.currentTime = options.currentTime !== void 0 ? options.currentTime : Infinity;
2083
+ this.showPoints = options.showPoints !== void 0 ? options.showPoints : true;
2084
+ this.showTips = options.showTips !== void 0 ? options.showTips : true;
2085
+ this.lineStyle = options.lineStyle || "uniform";
2086
+ this.strokeWidth = options.strokeWidth || 2;
2087
+ this.strokeWidthRange = options.strokeWidthRange || [1, 6];
2088
+ this.opacityRange = options.opacityRange || [0.3, 1];
2089
+ if (options.points && Array.isArray(options.points)) {
2090
+ this.trajPoints = options.points.map((p) => {
2091
+ if (p instanceof Point) return p;
2092
+ const pt = new Point({ dimensions: [p.x, p.y], meta: { time: p.time, label: p.label } });
2093
+ pt.x = p.x;
2094
+ pt.y = p.y;
2095
+ return pt;
2096
+ });
2097
+ }
2098
+ }
2099
+ get activePoints() {
2100
+ if (this.currentTime === Infinity) {
2101
+ return this.trajPoints;
2102
+ }
2103
+ return this.trajPoints.filter((p) => {
2104
+ const t = p.meta && typeof p.meta.time === "number" ? p.meta.time : 0;
2105
+ return t <= this.currentTime;
2106
+ });
2107
+ }
2108
+ get path() {
2109
+ return this.pathPoints.reduce((acc, pt, idx) => {
2110
+ return idx === 0 ? `M${pt.x},${pt.y}` : `${acc} L${pt.x},${pt.y}`;
2111
+ }, "");
2112
+ }
2113
+ get pathPoints() {
2114
+ const pts = this.activePoints;
2115
+ if (pts.length < 2) return [];
2116
+ if (this.smooth === "poly") {
2117
+ if (this.polyCoeffsX.length > 0 && this.polyCoeffsY.length > 0) {
2118
+ return sampleCurve(
2119
+ { degree: this.polyCoeffsX.length - 1, coeffsX: this.polyCoeffsX, coeffsY: this.polyCoeffsY },
2120
+ TRAJECTORY_CURVE_STEPS
2121
+ );
2122
+ }
2123
+ const fit = fitTrajectory(
2124
+ pts.map((p) => ({ x: p.x, y: p.y })),
2125
+ {
2126
+ maxDegree: this.maxDegree,
2127
+ degreeSelection: this.degreeSelection,
2128
+ selectionSet: this.trajPoints.map((p) => ({ x: p.x, y: p.y }))
2129
+ }
2130
+ );
2131
+ if (fit) {
2132
+ this.selectedDegree = fit.degree;
2133
+ return sampleCurve(fit, TRAJECTORY_CURVE_STEPS);
2134
+ }
2135
+ }
2136
+ return pts.map((pt) => ({ x: pt.x, y: pt.y }));
2137
+ }
2138
+ };
2139
+ __name(_Trajectory, "Trajectory");
2140
+ let Trajectory = _Trajectory;
1875
2141
  const zoom_level = 1;
1876
2142
  const _Edge = class _Edge extends Line {
1877
2143
  constructor(data, from, to, weight = 0) {
@@ -1919,7 +2185,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
1919
2185
  }
1920
2186
  get path() {
1921
2187
  let _this = __privateGet(this, _edge);
1922
- let _mid = _this.from_point, head = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.7), nock = Tools.Geometry.Line.point_along([[_this.to.x, _this.to.y], _mid], 0.85), mid = Tools.Geometry.Line.midpoint(head, nock), rot_mid = Tools.Geometry.Line.rotate(_this.line, mid), rot_nock = Tools.Geometry.Line.rotate(_this.line, nock), mid_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_mid), mid_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_mid), nock_int0 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[0]], rot_nock), nock_int1 = Tools.Geometry.Line.intersection([_mid, _this.base_pts[1]], rot_nock);
2188
+ 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);
1923
2189
  let p = `M${head.join(" ")} ${mid_int0.join(" ")} ${nock_int0.join(" ")} ${mid.join(" ")} ${nock_int1.join(" ")} ${mid_int1.join(" ")}z`;
1924
2190
  return p;
1925
2191
  }
@@ -2204,11 +2470,22 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2204
2470
  const extent = this.graph.__data__.extremes.x_max;
2205
2471
  return (extent > 0 ? extent : 1) * 6e-3 * this.__data__.size;
2206
2472
  }
2473
+ /** Optional halo stroke (e.g. white outline on unit points to separate
2474
+ * overlapping dots in dense clusters). Width scales with the dot so it holds
2475
+ * up at any zoom. No-op unless the object requests it via meta.stroke. */
2476
+ applyStroke(el) {
2477
+ var _a;
2478
+ const stroke = (_a = this.__data__.meta) == null ? void 0 : _a.stroke;
2479
+ if (!stroke) return;
2480
+ el.setAttribute("stroke", stroke);
2481
+ el.setAttribute("stroke-width", (this.computeRadius() * 0.22).toString());
2482
+ }
2207
2483
  create() {
2208
2484
  const tag = this.isSquare ? "rect" : "circle";
2209
2485
  const el = document.createElementNS("http://www.w3.org/2000/svg", tag);
2210
2486
  el.setAttribute("data-point-id", this.__data__.ID);
2211
2487
  el.setAttribute("fill", this.__data__.color.toString());
2488
+ this.applyStroke(el);
2212
2489
  const r = this.computeRadius();
2213
2490
  if (this.isSquare) {
2214
2491
  el.setAttribute("x", (this.x - r).toString());
@@ -2246,6 +2523,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2246
2523
  }
2247
2524
  const r = this.computeRadius();
2248
2525
  this.svg_element.setAttribute("fill", this.__data__.color.toString());
2526
+ this.applyStroke(this.svg_element);
2249
2527
  if (this.isSquare) {
2250
2528
  this.svg_element.setAttribute("x", (this.x - r).toString());
2251
2529
  this.svg_element.setAttribute("y", (this.y - r).toString());
@@ -2264,6 +2542,141 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2264
2542
  };
2265
2543
  __name(_QEPoint, "QEPoint");
2266
2544
  let QEPoint = _QEPoint;
2545
+ const _QETrajectory = class _QETrajectory extends Drawable {
2546
+ constructor(__data__, parent, graph, __children__ = new QEArray()) {
2547
+ super(__data__, parent, graph, __children__);
2548
+ this.__data__ = __data__;
2549
+ this.parent = parent;
2550
+ this.graph = graph;
2551
+ this.__children__ = __children__;
2552
+ this.__name__ = "trajectory";
2553
+ this._path_el = null;
2554
+ this._segments_group = null;
2555
+ this._tip_el = null;
2556
+ this._points_group = null;
2557
+ this.svg_element = this.create();
2558
+ }
2559
+ // Trajectory styling is specified in display pixels, while SVG paths use
2560
+ // graph coordinates. Convert at render time so marks stay legible at any scale.
2561
+ graphUnitsForPixels(pixels) {
2562
+ var _a;
2563
+ const extent = this.graph.__data__.extremes.x_max || 1;
2564
+ const width = ((_a = this.graph.svg_element) == null ? void 0 : _a.getBoundingClientRect().width) || 0;
2565
+ return width > 0 ? pixels * (2 * extent / width) : pixels * extent * 6e-3;
2566
+ }
2567
+ create() {
2568
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
2569
+ group.setAttribute("class", "qe-trajectory-group");
2570
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
2571
+ path.setAttribute("fill", "none");
2572
+ path.setAttribute("stroke", this.__data__.color.toString());
2573
+ path.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
2574
+ group.appendChild(path);
2575
+ this._path_el = path;
2576
+ const segmentsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
2577
+ segmentsGroup.setAttribute("class", "qe-trajectory-segments");
2578
+ group.appendChild(segmentsGroup);
2579
+ this._segments_group = segmentsGroup;
2580
+ const pointsGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
2581
+ pointsGroup.setAttribute("class", "qe-trajectory-points");
2582
+ group.appendChild(pointsGroup);
2583
+ this._points_group = pointsGroup;
2584
+ if (this.__data__.showTips) {
2585
+ const tip = document.createElementNS("http://www.w3.org/2000/svg", "circle");
2586
+ tip.setAttribute("fill", this.__data__.color.toString());
2587
+ tip.setAttribute("r", this.graphUnitsForPixels(4).toString());
2588
+ tip.setAttribute("stroke", "#ffffff");
2589
+ tip.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
2590
+ group.appendChild(tip);
2591
+ this._tip_el = tip;
2592
+ }
2593
+ this.svg_element = group;
2594
+ return group;
2595
+ }
2596
+ update() {
2597
+ if (!this.svg_element) {
2598
+ this.create();
2599
+ }
2600
+ if (!this.svg_element.parentElement) {
2601
+ this.parent.appendChild(this.svg_element);
2602
+ }
2603
+ const dStr = this.__data__.path;
2604
+ if (this._path_el) {
2605
+ this._path_el.setAttribute("d", dStr);
2606
+ this._path_el.setAttribute("stroke", this.__data__.color.toString());
2607
+ this._path_el.setAttribute("stroke-width", this.graphUnitsForPixels(this.__data__.strokeWidth).toString());
2608
+ this._path_el.style.display = this.__data__.lineStyle === "tapered" ? "none" : "block";
2609
+ }
2610
+ if (this._segments_group) {
2611
+ this._segments_group.innerHTML = "";
2612
+ if (this.__data__.lineStyle === "tapered") {
2613
+ const pathPts = this.__data__.pathPoints;
2614
+ const nSegments = pathPts.length - 1;
2615
+ const [minWidth, maxWidth] = this.__data__.strokeWidthRange;
2616
+ const [minOpacity, maxOpacity] = this.__data__.opacityRange;
2617
+ for (let i = 0; i < nSegments; i++) {
2618
+ const start = pathPts[i];
2619
+ const end = pathPts[i + 1];
2620
+ const progress = nSegments <= 1 ? 1 : i / (nSegments - 1);
2621
+ const width = minWidth + (maxWidth - minWidth) * progress;
2622
+ const opacity = minOpacity + (maxOpacity - minOpacity) * progress;
2623
+ const segment = document.createElementNS("http://www.w3.org/2000/svg", "path");
2624
+ segment.setAttribute("d", `M${start.x},${start.y} L${end.x},${end.y}`);
2625
+ segment.setAttribute("fill", "none");
2626
+ segment.setAttribute("stroke", this.__data__.color.toString());
2627
+ segment.setAttribute("stroke-linecap", "round");
2628
+ segment.setAttribute("stroke-linejoin", "round");
2629
+ segment.setAttribute("stroke-width", this.graphUnitsForPixels(width).toString());
2630
+ segment.setAttribute("opacity", opacity.toString());
2631
+ this._segments_group.appendChild(segment);
2632
+ }
2633
+ this._segments_group.style.display = "block";
2634
+ } else {
2635
+ this._segments_group.style.display = "none";
2636
+ }
2637
+ }
2638
+ const pts = this.__data__.activePoints;
2639
+ if (this._points_group) {
2640
+ if (this.__data__.showPoints && pts.length > 0) {
2641
+ this._points_group.innerHTML = "";
2642
+ pts.forEach((pt) => {
2643
+ var _a;
2644
+ const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle");
2645
+ dot.setAttribute("cx", pt.x.toString());
2646
+ dot.setAttribute("cy", pt.y.toString());
2647
+ dot.setAttribute("r", this.graphUnitsForPixels(2.5).toString());
2648
+ dot.setAttribute("fill", this.__data__.color.toString());
2649
+ dot.setAttribute("opacity", "0.7");
2650
+ (_a = this._points_group) == null ? void 0 : _a.appendChild(dot);
2651
+ });
2652
+ this._points_group.style.display = "block";
2653
+ } else {
2654
+ this._points_group.style.display = "none";
2655
+ }
2656
+ }
2657
+ if (this._tip_el && this.__data__.showTips) {
2658
+ if (pts.length > 0) {
2659
+ const lastPt = pts[pts.length - 1];
2660
+ this._tip_el.setAttribute("cx", lastPt.x.toString());
2661
+ this._tip_el.setAttribute("cy", lastPt.y.toString());
2662
+ this._tip_el.setAttribute("fill", this.__data__.color.toString());
2663
+ this._tip_el.setAttribute("r", this.graphUnitsForPixels(4).toString());
2664
+ this._tip_el.setAttribute("stroke-width", this.graphUnitsForPixels(1.5).toString());
2665
+ this._tip_el.style.display = "block";
2666
+ } else {
2667
+ this._tip_el.style.display = "none";
2668
+ }
2669
+ }
2670
+ }
2671
+ draw() {
2672
+ if (this.svg_element && !this.svg_element.parentElement) {
2673
+ this.parent.appendChild(this.svg_element);
2674
+ }
2675
+ this.update();
2676
+ }
2677
+ };
2678
+ __name(_QETrajectory, "QETrajectory");
2679
+ let QETrajectory = _QETrajectory;
2267
2680
  const { ArrayTools } = Tools;
2268
2681
  const _QEArray = class _QEArray extends Array {
2269
2682
  constructor(...args) {
@@ -2323,7 +2736,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2323
2736
  "Edge": QELine,
2324
2737
  "TaperedEdge": QELine,
2325
2738
  "TaperedWeightedMidpointEdge": QELine,
2326
- "NodeEdge": QELine
2739
+ "NodeEdge": QELine,
2740
+ "Trajectory": QETrajectory
2327
2741
  };
2328
2742
  const _QELayer = class _QELayer {
2329
2743
  constructor(__data__, parent, graph, __children__ = new QEArray()) {
@@ -2347,8 +2761,14 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2347
2761
  (obj) => !this.__children__.some((child) => childId(child) === obj.ID)
2348
2762
  );
2349
2763
  adding.forEach((obj) => {
2350
- const obj_name = obj.constructor.name;
2351
- const ena_element = new QEElementConstructors[obj_name](obj, this.svg_element, this.graph);
2764
+ const obj_name = obj._typeName || obj.constructor.name;
2765
+ let constructor = QEElementConstructors[obj_name];
2766
+ if (!constructor) {
2767
+ if (obj instanceof Trajectory) constructor = QETrajectory;
2768
+ else if (obj instanceof Point) constructor = QEPoint;
2769
+ else constructor = QELine;
2770
+ }
2771
+ const ena_element = new constructor(obj, this.svg_element, this.graph);
2352
2772
  this.__children__.push(ena_element);
2353
2773
  });
2354
2774
  updating.forEach((child) => {
@@ -2540,10 +2960,22 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2540
2960
  __name(_QEEdgesElement, "QEEdgesElement");
2541
2961
  _QEEdgesElement.tagName = "qe-edges";
2542
2962
  let QEEdgesElement = _QEEdgesElement;
2963
+ const _QETrajectoriesElement = class _QETrajectoriesElement extends HTMLElement {
2964
+ };
2965
+ __name(_QETrajectoriesElement, "QETrajectoriesElement");
2966
+ _QETrajectoriesElement.tagName = "qe-trajectories";
2967
+ let QETrajectoriesElement = _QETrajectoriesElement;
2968
+ const _QEAxesElement = class _QEAxesElement extends HTMLElement {
2969
+ };
2970
+ __name(_QEAxesElement, "QEAxesElement");
2971
+ _QEAxesElement.tagName = "qe-axes";
2972
+ let QEAxesElement = _QEAxesElement;
2543
2973
  customElements.define(QENodesElement.tagName, QENodesElement);
2544
2974
  customElements.define(QEMeansElement.tagName, QEMeansElement);
2545
2975
  customElements.define(QEPointsElement.tagName, QEPointsElement);
2546
2976
  customElements.define(QEEdgesElement.tagName, QEEdgesElement);
2977
+ customElements.define(QETrajectoriesElement.tagName, QETrajectoriesElement);
2978
+ customElements.define(QEAxesElement.tagName, QEAxesElement);
2547
2979
  const GROUP_PALETTE = [
2548
2980
  "#4477AA",
2549
2981
  "#EE6677",
@@ -2617,6 +3049,10 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2617
3049
  this._nodeLabels = /* @__PURE__ */ new Map();
2618
3050
  this._meanLabels = /* @__PURE__ */ new Map();
2619
3051
  this._pointLabels = /* @__PURE__ */ new Map();
3052
+ this._showAxes = false;
3053
+ this._axisXText = null;
3054
+ this._axisYText = null;
3055
+ this._axisLabelEls = /* @__PURE__ */ new Map();
2620
3056
  this.graph = this;
2621
3057
  this.parent = null;
2622
3058
  this.__data__ = new Graph(DefaultGraphOptions);
@@ -2624,7 +3060,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2624
3060
  this.svg_element = this.create();
2625
3061
  console.log("Created ena-graph svg element: ", this.svg_element);
2626
3062
  document.addEventListener("model-updated", (e) => {
2627
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
3063
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
2628
3064
  if (this._vizena && e.target !== this._vizena) return;
2629
3065
  const model = e.detail;
2630
3066
  if (!model) return;
@@ -2634,6 +3070,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2634
3070
  const nodeIdCol = model.node_id_col;
2635
3071
  const groupColName = model.group_col;
2636
3072
  const config = this._readLayerConfig();
3073
+ this._showAxes = config.showAxes;
3074
+ this._axisXText = config.axisXLabel === null ? null : config.axisXLabel || String((dims == null ? void 0 : dims[0]) ?? "x");
3075
+ this._axisYText = config.axisYLabel === null ? null : config.axisYLabel || String((dims == null ? void 0 : dims[1]) ?? "y");
2637
3076
  const groupColorMap = {};
2638
3077
  if (groupColName && ((_a = model.points) == null ? void 0 : _a.names)) {
2639
3078
  const groupCol = model.points.col(groupColName);
@@ -2667,7 +3106,9 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2667
3106
  }, "getExtent");
2668
3107
  const maxNodeExtent = ((_c = model.nodes) == null ? void 0 : _c.rows) ? getExtent(model.nodes.rows()) : 0;
2669
3108
  const maxPointExtent = ((_d = model.points) == null ? void 0 : _d.rows) ? getExtent(model.points.rows()) : 0;
2670
- const pointScale = scalePointsEnabled && maxPointExtent > 0 && maxNodeExtent > 0 ? maxNodeExtent / maxPointExtent : 1;
3109
+ const maxTrajExtent = ((_e = model.trajectories) == null ? void 0 : _e.rows) ? getExtent(model.trajectories.rows()) : 0;
3110
+ const effectivePointExtent = maxPointExtent > 0 ? maxPointExtent : maxTrajExtent;
3111
+ const pointScale = scalePointsEnabled && effectivePointExtent > 0 && maxNodeExtent > 0 ? maxNodeExtent / effectivePointExtent : 1;
2671
3112
  const labelNodes = config.labelNodes;
2672
3113
  const labelMeans = config.labelMeans;
2673
3114
  const labelPoints = config.labelPoints;
@@ -2690,7 +3131,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2690
3131
  }, "rowToPoint");
2691
3132
  const groupSums = {};
2692
3133
  const showUnitPoints = config.showPoints;
2693
- if ((_e = model.points) == null ? void 0 : _e.rows) {
3134
+ if ((_f = model.points) == null ? void 0 : _f.rows) {
2694
3135
  const pointsLayer = this.__children__.search("__children__", "points");
2695
3136
  if (pointsLayer) {
2696
3137
  model.points.rows().forEach((row) => {
@@ -2699,6 +3140,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2699
3140
  const colorStr = group ? groupColorMap[group] ?? "#888888" : "#888888";
2700
3141
  if (showUnitPoints) {
2701
3142
  const pt = rowToPoint(row, unitIdCol, colorStr, pointScale);
3143
+ pt.size = 2.5;
3144
+ pt.meta = { ...pt.meta ?? {}, stroke: "#ffffff" };
2702
3145
  this.__data__.points.set(pt.ID, pt);
2703
3146
  pointsLayer.__data__.objects.set(pt.ID, pt);
2704
3147
  }
@@ -2716,7 +3159,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2716
3159
  groupSums[group].n += 1;
2717
3160
  }
2718
3161
  });
2719
- const hasGroupsFrame = !!(((_f = model.groups) == null ? void 0 : _f.rows) && model.groups.rows().length > 0);
3162
+ const hasGroupsFrame = !!(((_g = model.groups) == null ? void 0 : _g.rows) && model.groups.rows().length > 0);
2720
3163
  const activeGroupRows = config.showMeans && hasGroupsFrame ? model.groups.rows().filter((r) => {
2721
3164
  const g = r.rowName ?? String(r[0]);
2722
3165
  return activeMeanGroups.size === 0 || activeMeanGroups.has(g);
@@ -2802,7 +3245,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2802
3245
  this._ciRects.delete(g);
2803
3246
  }
2804
3247
  });
2805
- if ((_g = model.confidence) == null ? void 0 : _g.rows) {
3248
+ if ((_h = model.confidence) == null ? void 0 : _h.rows) {
2806
3249
  model.confidence.rows().forEach((row) => {
2807
3250
  const groupIdx = row.names.indexOf(groupColName ?? "group");
2808
3251
  const group = groupIdx >= 0 ? String(row[groupIdx]) : row.rowName ?? null;
@@ -2862,7 +3305,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2862
3305
  });
2863
3306
  }
2864
3307
  }
2865
- if ((_h = model.outlier) == null ? void 0 : _h.rows) {
3308
+ if ((_i = model.outlier) == null ? void 0 : _i.rows) {
2866
3309
  this._outlierRects.forEach((el, g) => {
2867
3310
  if (!groupColorMap[g]) {
2868
3311
  el.remove();
@@ -2951,20 +3394,73 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
2951
3394
  pointsLayer.__data__.__state__.setVariable("layer_updated", pointsLayer.__data__);
2952
3395
  }
2953
3396
  }
3397
+ const trajectoriesLayer = this.__children__.search("__children__", "trajectories");
3398
+ const trajectoriesEnabled = config.showTrajectories;
3399
+ if (trajectoriesEnabled && trajectoriesLayer) {
3400
+ trajectoriesLayer.__data__.objects.clear();
3401
+ this.__data__.trajectories.clear();
3402
+ const trajSource = ((_j = model.trajectories) == null ? void 0 : _j.rows) ? model.trajectories : model.points;
3403
+ if (trajSource == null ? void 0 : trajSource.rows) {
3404
+ const unitRows = {};
3405
+ const selectedUnits = config.trajectoryUnits ? config.trajectoryUnits.split(",").map((s) => s.trim()) : null;
3406
+ trajSource.rows().forEach((row) => {
3407
+ 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;
3408
+ const unit = uIdx >= 0 ? String(row[uIdx]) : row.rowName || "unit-1";
3409
+ if (selectedUnits && !selectedUnits.includes(unit)) return;
3410
+ const xIdx = row.names.indexOf(dims[0]);
3411
+ const yIdx = dims.length > 1 ? row.names.indexOf(dims[1]) : -1;
3412
+ 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;
3413
+ const time = tIdx >= 0 ? Number(row[tIdx]) : unitRows[unit] ? unitRows[unit].length + 1 : 1;
3414
+ if (xIdx >= 0 && yIdx >= 0) {
3415
+ if (!unitRows[unit]) unitRows[unit] = [];
3416
+ unitRows[unit].push({
3417
+ x: Number(row[xIdx]) * pointScale,
3418
+ y: -Number(row[yIdx]) * pointScale,
3419
+ time
3420
+ });
3421
+ }
3422
+ });
3423
+ const unitKeys = Object.keys(unitRows);
3424
+ unitKeys.forEach((unitKey, uIdx) => {
3425
+ const rawPts = unitRows[unitKey];
3426
+ if (rawPts.length === 0) return;
3427
+ const colorStr = GROUP_PALETTE[uIdx % GROUP_PALETTE.length];
3428
+ const trajObj = new Trajectory({
3429
+ ID: `traj-${unitKey}`,
3430
+ unitId: unitKey,
3431
+ color: new Color(colorStr),
3432
+ lineStyle: config.trajectoryLineStyle,
3433
+ strokeWidth: config.trajectoryStrokeWidth,
3434
+ strokeWidthRange: config.trajectoryStrokeWidthRange,
3435
+ opacityRange: config.trajectoryOpacityRange,
3436
+ smooth: config.trajectorySmooth,
3437
+ maxDegree: config.trajectoryMaxDegree,
3438
+ degreeSelection: config.trajectoryDegreeSelection,
3439
+ currentTime: config.trajectoryPlayhead ?? Infinity,
3440
+ showPoints: config.trajectoryShowPoints,
3441
+ showTips: config.trajectoryShowTips,
3442
+ points: rawPts.map((p) => ({ x: p.x, y: p.y, time: p.time }))
3443
+ });
3444
+ this.__data__.trajectories.set(unitKey, trajObj);
3445
+ trajectoriesLayer.__data__.objects.set(trajObj.ID, trajObj);
3446
+ });
3447
+ }
3448
+ trajectoriesLayer.__data__.__state__.setVariable("layer_updated", trajectoriesLayer.__data__);
3449
+ }
2954
3450
  const networkLayer = this.__children__.search("__children__", "network");
2955
3451
  const edgeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edges");
2956
3452
  const nodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "nodes");
2957
3453
  const edgenodeLayer = networkLayer == null ? void 0 : networkLayer.__children__.search("__children__", "edgenodes");
2958
3454
  const edgesEnabled = config.showEdges;
2959
- if (((_i = model.nodes) == null ? void 0 : _i.rows) && nodeLayer) {
3455
+ if (((_k = model.nodes) == null ? void 0 : _k.rows) && nodeLayer) {
2960
3456
  model.nodes.rows().forEach((row) => {
2961
3457
  const pt = rowToPoint(row, nodeIdCol, "#444444");
2962
3458
  pt.size = 2;
2963
3459
  nodeLayer.__data__.objects.set(pt.ID, pt);
2964
3460
  });
2965
- if (edgesEnabled && edgeLayer && ((_j = model.edges) == null ? void 0 : _j.rows)) {
3461
+ if (edgesEnabled && edgeLayer && ((_l = model.edges) == null ? void 0 : _l.rows)) {
2966
3462
  const unitGroupMap = {};
2967
- if (groupColName && ((_k = model.points) == null ? void 0 : _k.rows)) {
3463
+ if (groupColName && ((_m = model.points) == null ? void 0 : _m.rows)) {
2968
3464
  model.points.rows().forEach((row) => {
2969
3465
  const qIdx = row.names.indexOf(unitIdCol);
2970
3466
  const gIdx = row.names.indexOf(groupColName);
@@ -3090,7 +3586,6 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3090
3586
  totalWeightByPair[key] = (totalWeightByPair[key] ?? 0) + weight;
3091
3587
  });
3092
3588
  }
3093
- ((_l = this.__data__.extremes) == null ? void 0 : _l.x_max) ?? 1;
3094
3589
  drawEdges.forEach(({ col, pair, weight, color }) => {
3095
3590
  const fromNode = nodeLayer.__data__.objects.get(pair[0]);
3096
3591
  const toNode = nodeLayer.__data__.objects.get(pair[1]);
@@ -3212,6 +3707,8 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3212
3707
  const meansEl = this.querySelector("qe-means");
3213
3708
  const pointsEl = this.querySelector("qe-points");
3214
3709
  const edgesEl = this.querySelector("qe-edges");
3710
+ const trajectoriesEl = this.querySelector("qe-trajectories");
3711
+ const axesEl = this.querySelector("qe-axes");
3215
3712
  const labelsShorthand = this.getAttribute("labels");
3216
3713
  return {
3217
3714
  // Nodes — always rendered (coordinate anchors); <qe-nodes> controls labels.
@@ -3239,9 +3736,35 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3239
3736
  edgeGroup: (edgesEl == null ? void 0 : edgesEl.getAttribute("group")) ?? null,
3240
3737
  edgeUnit: (edgesEl == null ? void 0 : edgesEl.getAttribute("unit")) ?? null,
3241
3738
  edgeCompare: (edgesEl == null ? void 0 : edgesEl.getAttribute("compare")) ?? null,
3242
- edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null
3739
+ edgeAlso: (edgesEl == null ? void 0 : edgesEl.getAttribute("also")) ?? null,
3740
+ // Trajectories — presence of <qe-trajectories> = draw cognitive trajectories
3741
+ showTrajectories: !!trajectoriesEl,
3742
+ trajectoryUnits: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("units")) ?? null,
3743
+ trajectorySmooth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("smooth")) ?? "none",
3744
+ trajectoryMaxDegree: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("max-degree")) ? parseInt(trajectoriesEl.getAttribute("max-degree"), 10) : 3,
3745
+ trajectoryDegreeSelection: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("degree-selection")) ?? "loocv_2d",
3746
+ trajectoryShowPoints: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("points")) !== "false",
3747
+ trajectoryShowTips: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("tips")) !== "false",
3748
+ trajectoryPlayhead: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("playhead")) ? parseFloat(trajectoriesEl.getAttribute("playhead")) : null,
3749
+ trajectoryLineStyle: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("line-style")) ?? "uniform",
3750
+ trajectoryStrokeWidth: (trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width")) ? parseFloat(trajectoriesEl.getAttribute("stroke-width")) : 2,
3751
+ trajectoryStrokeWidthRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("stroke-width-range"), [1, 6]),
3752
+ trajectoryOpacityRange: this._parseNumberPair(trajectoriesEl == null ? void 0 : trajectoriesEl.getAttribute("opacity-range"), [0.3, 1]),
3753
+ // Axes — presence of <qe-axes> = draw axis lines. The x / y attributes add
3754
+ // a per-axis label: a value is used verbatim, an empty attribute falls back
3755
+ // to the dimension name (resolved later, where dims are known). null = no
3756
+ // label for that axis.
3757
+ showAxes: !!axesEl,
3758
+ axisXLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("x")) ? axesEl.getAttribute("x") ?? "" : null,
3759
+ axisYLabel: (axesEl == null ? void 0 : axesEl.hasAttribute("y")) ? axesEl.getAttribute("y") ?? "" : null
3243
3760
  };
3244
3761
  }
3762
+ _parseNumberPair(value, fallback) {
3763
+ if (!value) return fallback;
3764
+ const parts = value.split(",").map((part) => Number(part.trim()));
3765
+ if (parts.length < 2 || !parts.every(Number.isFinite)) return fallback;
3766
+ return [parts[0], parts[1]];
3767
+ }
3245
3768
  attributeChangedCallback(_name2, oldValue, newValue) {
3246
3769
  if (oldValue === newValue) return;
3247
3770
  this._scheduleRerender();
@@ -3250,6 +3773,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3250
3773
  var _a;
3251
3774
  const w = this.getAttribute("width");
3252
3775
  const h = this.getAttribute("height");
3776
+ this.style.display = "block";
3253
3777
  if (w) this.style.width = isNaN(Number(w)) ? w : `${w}px`;
3254
3778
  if (h) this.style.height = isNaN(Number(h)) ? h : `${h}px`;
3255
3779
  this.appendChild(this.svg_element);
@@ -3270,7 +3794,19 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3270
3794
  "labels",
3271
3795
  "label-nodes",
3272
3796
  "label-means",
3273
- "label-points"
3797
+ "label-points",
3798
+ "units",
3799
+ "smooth",
3800
+ "degree",
3801
+ "points",
3802
+ "tips",
3803
+ "playhead",
3804
+ "line-style",
3805
+ "stroke-width",
3806
+ "stroke-width-range",
3807
+ "opacity-range",
3808
+ "x",
3809
+ "y"
3274
3810
  ]
3275
3811
  });
3276
3812
  const forId = this.getAttribute("for");
@@ -3297,6 +3833,10 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3297
3833
  create() {
3298
3834
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3299
3835
  svg.classList.add("graph");
3836
+ svg.style.display = "block";
3837
+ svg.style.width = "100%";
3838
+ svg.style.height = "100%";
3839
+ svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
3300
3840
  svg.setAttribute("viewBox", this.__data__.extremes.as_viewBox());
3301
3841
  svg.setAttribute("transform", this.__data__.transform.toString());
3302
3842
  svg.setAttribute("role", "figure");
@@ -3423,6 +3963,56 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3423
3963
  this.update_layers();
3424
3964
  this.update_labels();
3425
3965
  this.update_extremes();
3966
+ this.update_axes();
3967
+ }
3968
+ /**
3969
+ * Axes (<qe-axes>): toggle the axis-line cross by presence, and draw/position
3970
+ * per-axis labels at the positive ends using the current extent. Labels are
3971
+ * cached SVG <text> nodes in the labels group; null text removes them.
3972
+ */
3973
+ update_axes() {
3974
+ const axesLayer = this.__children__.search("__children__", "axes");
3975
+ if (axesLayer == null ? void 0 : axesLayer.svg_element) {
3976
+ axesLayer.svg_element.style.display = this._showAxes ? "" : "none";
3977
+ }
3978
+ const parent = this._labelsGroup;
3979
+ const extent = this.__data__.extremes.x_max;
3980
+ if (!parent || extent <= 0) return;
3981
+ const fontSize = extent * 0.06;
3982
+ const gap = fontSize * 0.6;
3983
+ const place = /* @__PURE__ */ __name((axis, text) => {
3984
+ let el = this._axisLabelEls.get(axis);
3985
+ if (!this._showAxes || text == null) {
3986
+ if (el) {
3987
+ el.remove();
3988
+ this._axisLabelEls.delete(axis);
3989
+ }
3990
+ return;
3991
+ }
3992
+ if (!el) {
3993
+ el = document.createElementNS("http://www.w3.org/2000/svg", "text");
3994
+ el.setAttribute("data-axis-label", axis);
3995
+ el.setAttribute("pointer-events", "none");
3996
+ parent.appendChild(el);
3997
+ this._axisLabelEls.set(axis, el);
3998
+ }
3999
+ el.textContent = text;
4000
+ el.setAttribute("font-size", fontSize.toString());
4001
+ el.setAttribute("font-family", "system-ui, sans-serif");
4002
+ el.setAttribute("font-weight", "600");
4003
+ el.setAttribute("fill", "#556");
4004
+ if (axis === "x") {
4005
+ el.setAttribute("x", (extent + gap).toString());
4006
+ el.setAttribute("y", (fontSize * 0.35).toString());
4007
+ el.setAttribute("text-anchor", "start");
4008
+ } else {
4009
+ el.setAttribute("x", "0");
4010
+ el.setAttribute("y", (-extent - gap).toString());
4011
+ el.setAttribute("text-anchor", "middle");
4012
+ }
4013
+ }, "place");
4014
+ place("x", this._axisXText);
4015
+ place("y", this._axisYText);
3426
4016
  }
3427
4017
  update_labels() {
3428
4018
  this._nodeLabels.forEach((lbl) => lbl.update());
@@ -3495,6 +4085,7 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3495
4085
  exports2.DataFrameRow = DataFrameRow;
3496
4086
  exports2.Edge = Edge;
3497
4087
  exports2.Graph = Graph;
4088
+ exports2.QEAxesElement = QEAxesElement;
3498
4089
  exports2.QEEdgesElement = QEEdgesElement;
3499
4090
  exports2.QEGraphElement = QEGraphElement;
3500
4091
  exports2.QELayer = QELayer;
@@ -3503,8 +4094,16 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
3503
4094
  exports2.QENodesElement = QENodesElement;
3504
4095
  exports2.QEPoint = QEPoint;
3505
4096
  exports2.QEPointsElement = QEPointsElement;
4097
+ exports2.QETrajectoriesElement = QETrajectoriesElement;
4098
+ exports2.QETrajectory = QETrajectory;
3506
4099
  exports2.QEVisualElement = QEVisualElement;
4100
+ exports2.TRAJECTORY_ZERO_EPS = TRAJECTORY_ZERO_EPS;
4101
+ exports2.Trajectory = Trajectory;
3507
4102
  exports2.WeightedColor = WeightedColor;
4103
+ exports2.fitTrajectory = fitTrajectory;
4104
+ exports2.nearOriginFilter = nearOriginFilter;
4105
+ exports2.sampleCurve = sampleCurve;
4106
+ exports2.selectDegree = selectDegree;
3508
4107
  exports2.vizENA = vizENA;
3509
4108
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
3510
4109
  }));