@yuneta/gobj-ui 7.23.154 → 7.23.155

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 CHANGED
@@ -1419,7 +1419,8 @@ layouts by three thin adapters in `c_g6_nodes_tree.js`:
1419
1419
  gained nothing on a whole level opened with the stepper, where every parent
1420
1420
  still needed the row of its children beside it. Against `tree`: regions
1421
1421
  only 10% of the width, one region open with a 24-device hall 21%, a whole
1422
- level open 41%. The tree grows taller instead; read it with elbow edges.
1422
+ level open 41%. The tree grows taller instead; with elbow edges or curves
1423
+ alike, since `7.23.155` a curve keeps off the cards too.
1423
1424
  - **`radial`** (`treedb-radial`) — the root in the middle, a ring per depth,
1424
1425
  every subtree an angular **sector** proportional to its leaves, and the
1425
1426
  radius of each ring the largest of three: one `ranksep` out from the ring
@@ -1481,7 +1482,16 @@ port run. Only the stub at the port is shared. Into a lower row of a stack of
1481
1482
  the compact tree an edge runs a **comb** (`7.23.154`): along the channel to a
1482
1483
  lane of its own in the stack's corridor, down it, and into the gap above its
1483
1484
  card; lanes go outermost first by row, so none crosses another, and a comb is
1484
- drawn as laid out, never routed. G6's `shortest-path` router is not used: it is
1485
+ drawn as laid out, never routed.
1486
+
1487
+ **Curved edges keep off the cards as well** (`7.23.155`). On a layout with
1488
+ rows a curve is `treedb-curve-v` / `-h`, a subclass of G6's cubic: the cubic
1489
+ as before, unless that very curve — the path G6 would draw, sampled, not the
1490
+ rectangle round it — would cross a card other than its own two, or the edge
1491
+ does not run forward and so would cross its own. Then it takes the way its
1492
+ elbow would (comb, detour, route, the same lanes and staggered turns) with
1493
+ corners rounded to 24px. On a layout with no rows the edges stay G6's plain
1494
+ cubic. G6's `shortest-path` router is not used: it is
1485
1495
  not exported, it
1486
1496
  falls back in silence to a route that crosses cards, and it rewrites its
1487
1497
  module defaults with every config it is given.
@@ -30168,6 +30168,112 @@ function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
30168
30168
  if (!route && near.length < others.length) route = orth_search(a, z, others.map((b) => box_expand(b, m)));
30169
30169
  return route || simple;
30170
30170
  }
30171
+ function path_points(path) {
30172
+ let pts = [];
30173
+ let cur = null;
30174
+ for (let c of path) if (c[0] === "M" || c[0] === "L") {
30175
+ cur = [c[1], c[2]];
30176
+ pts.push(cur);
30177
+ } else if (c[0] === "C" && cur) {
30178
+ let [x0, y0] = cur;
30179
+ for (let k = 1; k <= 16; k++) {
30180
+ let u = k / 16;
30181
+ let v = 1 - u;
30182
+ pts.push([v * v * v * x0 + 3 * v * v * u * c[1] + 3 * v * u * u * c[3] + u * u * u * c[5], v * v * v * y0 + 3 * v * v * u * c[2] + 3 * v * u * u * c[4] + u * u * u * c[6]]);
30183
+ }
30184
+ cur = [c[5], c[6]];
30185
+ } else if (c[0] === "Q" && cur) {
30186
+ let [x0, y0] = cur;
30187
+ for (let k = 1; k <= 16; k++) {
30188
+ let u = k / 16;
30189
+ let v = 1 - u;
30190
+ pts.push([v * v * x0 + 2 * v * u * c[1] + u * u * c[3], v * v * y0 + 2 * v * u * c[2] + u * u * c[4]]);
30191
+ }
30192
+ cur = [c[3], c[4]];
30193
+ }
30194
+ return pts;
30195
+ }
30196
+ /************************************************************
30197
+ * Does the G6 path `path` of a curved edge cross a card other than
30198
+ * its own two? Measured on the curve itself -- the path G6 would
30199
+ * draw, sampled -- and not on the rectangle round it, which took
30200
+ * every card the curve merely passed near: the ports of a row
30201
+ * stick well into the space above it. Its own two cards are left
30202
+ * out: the curve starts and ends on their ports.
30203
+ ************************************************************/
30204
+ function curve_hits(path, boxes, box_s, box_t) {
30205
+ let pts = path_points(path);
30206
+ if (pts.length < 2) return false;
30207
+ let bb = {
30208
+ x1: Infinity,
30209
+ y1: Infinity,
30210
+ x2: -Infinity,
30211
+ y2: -Infinity
30212
+ };
30213
+ for (let p of pts) {
30214
+ bb.x1 = Math.min(bb.x1, p[0]);
30215
+ bb.y1 = Math.min(bb.y1, p[1]);
30216
+ bb.x2 = Math.max(bb.x2, p[0]);
30217
+ bb.y2 = Math.max(bb.y2, p[1]);
30218
+ }
30219
+ for (let b of boxes || []) {
30220
+ if (same_box(b, box_s) || same_box(b, box_t) || !box_overlaps(b, box_expand(bb, 1))) continue;
30221
+ let inner = box_expand(b, -1);
30222
+ for (let k = 0; k + 1 < pts.length; k++) if (segment_hits(pts[k], pts[k + 1], inner)) return true;
30223
+ }
30224
+ return false;
30225
+ }
30226
+ /************************************************************
30227
+ * A G6 path (['M'|'L'|'Q', ...]) along `points` with every corner
30228
+ * rounded -- by `radius`, or by half the shorter of its two
30229
+ * segments, so a short run is never overshot. The rounding is the
30230
+ * quadratic G6's own polyline uses.
30231
+ ************************************************************/
30232
+ function rounded_path(points, radius) {
30233
+ let path = [[
30234
+ "M",
30235
+ points[0][0],
30236
+ points[0][1]
30237
+ ]];
30238
+ for (let i = 1; i + 1 < points.length; i++) {
30239
+ let a = points[i - 1];
30240
+ let m = points[i];
30241
+ let b = points[i + 1];
30242
+ let len_in = Math.hypot(m[0] - a[0], m[1] - a[1]);
30243
+ let len_out = Math.hypot(b[0] - m[0], b[1] - m[1]);
30244
+ let r = Math.min(radius, len_in / 2, len_out / 2);
30245
+ let collinear = Math.abs((m[0] - a[0]) * (b[1] - m[1]) - (m[1] - a[1]) * (b[0] - m[0])) < 1e-9;
30246
+ if (r <= 0 || collinear) {
30247
+ path.push([
30248
+ "L",
30249
+ m[0],
30250
+ m[1]
30251
+ ]);
30252
+ continue;
30253
+ }
30254
+ let p1 = [m[0] - (m[0] - a[0]) / len_in * r, m[1] - (m[1] - a[1]) / len_in * r];
30255
+ let p2 = [m[0] + (b[0] - m[0]) / len_out * r, m[1] + (b[1] - m[1]) / len_out * r];
30256
+ path.push([
30257
+ "L",
30258
+ p1[0],
30259
+ p1[1]
30260
+ ]);
30261
+ path.push([
30262
+ "Q",
30263
+ m[0],
30264
+ m[1],
30265
+ p2[0],
30266
+ p2[1]
30267
+ ]);
30268
+ }
30269
+ let last = points[points.length - 1];
30270
+ path.push([
30271
+ "L",
30272
+ last[0],
30273
+ last[1]
30274
+ ]);
30275
+ return path;
30276
+ }
30171
30277
  //#endregion
30172
30278
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
30173
30279
  function _typeof(o) {
@@ -55055,7 +55161,7 @@ Easing Functions from anime.js, they are tried and true, so, its better to use t
55055
55161
  var Quad = function Quad(t) {
55056
55162
  return Math.pow(t, 2);
55057
55163
  };
55058
- var Cubic = function Cubic(t) {
55164
+ var Cubic$1 = function Cubic(t) {
55059
55165
  return Math.pow(t, 3);
55060
55166
  };
55061
55167
  var Quart = function Quart(t) {
@@ -55192,10 +55298,10 @@ var EasingFunctions = {
55192
55298
  "out-quad": EaseOut(Quad),
55193
55299
  "in-out-quad": EaseInOut(Quad),
55194
55300
  "out-in-quad": EaseOutIn(Quad),
55195
- "in-cubic": Cubic,
55196
- "out-cubic": EaseOut(Cubic),
55197
- "in-out-cubic": EaseInOut(Cubic),
55198
- "out-in-cubic": EaseOutIn(Cubic),
55301
+ "in-cubic": Cubic$1,
55302
+ "out-cubic": EaseOut(Cubic$1),
55303
+ "in-out-cubic": EaseInOut(Cubic$1),
55304
+ "out-in-cubic": EaseOutIn(Cubic$1),
55199
55305
  "in-quart": Quart,
55200
55306
  "out-quart": EaseOut(Quart),
55201
55307
  "in-out-quart": EaseInOut(Quart),
@@ -57368,6 +57474,8 @@ function register_layouts(gobj) {
57368
57474
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
57369
57475
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-elbow-v", TreedbElbowV);
57370
57476
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-elbow-h", TreedbElbowH);
57477
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-curve-v", TreedbCurveV);
57478
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-curve-h", TreedbCurveH);
57371
57479
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
57372
57480
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "light", LightNode);
57373
57481
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "treedb-card", TreedbCard);
@@ -58062,7 +58170,9 @@ function layout_direction(gobj) {
58062
58170
  function edge_type_for(gobj) {
58063
58171
  let lr = layout_direction(gobj) === "LR";
58064
58172
  let rowless = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "rowless_layouts") || [];
58065
- if ((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") === "elbow" && !(0, _yuneta_gobj_js.str_in_list)(rowless, gobj.priv.layout)) return lr ? "treedb-elbow-h" : "treedb-elbow-v";
58173
+ let rows = !(0, _yuneta_gobj_js.str_in_list)(rowless, gobj.priv.layout);
58174
+ if (rows && (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") === "elbow") return lr ? "treedb-elbow-h" : "treedb-elbow-v";
58175
+ if (rows) return lr ? "treedb-curve-h" : "treedb-curve-v";
58066
58176
  return lr ? "cubic-horizontal" : "cubic";
58067
58177
  }
58068
58178
  /************************************************************
@@ -59666,6 +59776,40 @@ var TreedbElbowH = class extends _antv_g6.Polyline {
59666
59776
  return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59667
59777
  }
59668
59778
  };
59779
+ /************************************************************
59780
+ * A CURVED edge that keeps off the cards. G6's cubic, as before,
59781
+ * unless that very curve -- sampled, not guessed from the
59782
+ * rectangle round it -- would cross a card other than its own two,
59783
+ * or when it does not run forward and so would cross its own:
59784
+ * the card above a lower row of a stack, a row between a parent
59785
+ * and a far child. Then it goes the way an elbow would (the comb,
59786
+ * the detour, the route: the same lanes and staggered turns) with
59787
+ * round corners, so it still reads as a curve. `-v` for rows
59788
+ * stacked top to bottom, `-h` for columns.
59789
+ ************************************************************/
59790
+ function curve_key_path(edge, attributes, vertical, plain) {
59791
+ let [s, t] = edge.getEndpoints(attributes, false);
59792
+ let along = vertical ? 1 : 0;
59793
+ let box_s = elbow_box(edge.sourceNode);
59794
+ let box_t = elbow_box(edge.targetNode);
59795
+ let boxes = elbow_obstacles(edge);
59796
+ if (t[along] - s[along] > 20 && !curve_hits(plain, boxes, box_s, box_t)) return plain;
59797
+ return rounded_path([
59798
+ s,
59799
+ ...elbow_route(s, t, box_s, box_t, elbow_lane_of(edge), vertical, boxes, elbow_stagger_of(edge, vertical)),
59800
+ t
59801
+ ], 24);
59802
+ }
59803
+ var TreedbCurveV = class extends _antv_g6.Cubic {
59804
+ getKeyPath(attributes) {
59805
+ return curve_key_path(this, attributes, true, super.getKeyPath(attributes));
59806
+ }
59807
+ };
59808
+ var TreedbCurveH = class extends _antv_g6.CubicHorizontal {
59809
+ getKeyPath(attributes) {
59810
+ return curve_key_path(this, attributes, false, super.getKeyPath(attributes));
59811
+ }
59812
+ };
59669
59813
  var TreedbCompactLayout = class extends _antv_g6.BaseLayout {
59670
59814
  async execute(data, options) {
59671
59815
  let input = treedb_layout_input(data);
@@ -6,7 +6,7 @@ import { createJSONEditor } from "vanilla-jsoneditor";
6
6
  import { TabulatorFull } from "tabulator-tables";
7
7
  import * as maplibregl from "maplibre-gl";
8
8
  import uPlot from "uplot";
9
- import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HTML, HistoryEvent, NodeEvent, Polyline, Toolbar, ZoomCanvas, register } from "@antv/g6";
9
+ import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, Cubic, CubicHorizontal, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HTML, HistoryEvent, NodeEvent, Polyline, Toolbar, ZoomCanvas, register } from "@antv/g6";
10
10
  //#region \0rolldown/runtime.js
11
11
  var __create = Object.create;
12
12
  var __defProp = Object.defineProperty;
@@ -30163,6 +30163,112 @@ function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
30163
30163
  if (!route && near.length < others.length) route = orth_search(a, z, others.map((b) => box_expand(b, m)));
30164
30164
  return route || simple;
30165
30165
  }
30166
+ function path_points(path) {
30167
+ let pts = [];
30168
+ let cur = null;
30169
+ for (let c of path) if (c[0] === "M" || c[0] === "L") {
30170
+ cur = [c[1], c[2]];
30171
+ pts.push(cur);
30172
+ } else if (c[0] === "C" && cur) {
30173
+ let [x0, y0] = cur;
30174
+ for (let k = 1; k <= 16; k++) {
30175
+ let u = k / 16;
30176
+ let v = 1 - u;
30177
+ pts.push([v * v * v * x0 + 3 * v * v * u * c[1] + 3 * v * u * u * c[3] + u * u * u * c[5], v * v * v * y0 + 3 * v * v * u * c[2] + 3 * v * u * u * c[4] + u * u * u * c[6]]);
30178
+ }
30179
+ cur = [c[5], c[6]];
30180
+ } else if (c[0] === "Q" && cur) {
30181
+ let [x0, y0] = cur;
30182
+ for (let k = 1; k <= 16; k++) {
30183
+ let u = k / 16;
30184
+ let v = 1 - u;
30185
+ pts.push([v * v * x0 + 2 * v * u * c[1] + u * u * c[3], v * v * y0 + 2 * v * u * c[2] + u * u * c[4]]);
30186
+ }
30187
+ cur = [c[3], c[4]];
30188
+ }
30189
+ return pts;
30190
+ }
30191
+ /************************************************************
30192
+ * Does the G6 path `path` of a curved edge cross a card other than
30193
+ * its own two? Measured on the curve itself -- the path G6 would
30194
+ * draw, sampled -- and not on the rectangle round it, which took
30195
+ * every card the curve merely passed near: the ports of a row
30196
+ * stick well into the space above it. Its own two cards are left
30197
+ * out: the curve starts and ends on their ports.
30198
+ ************************************************************/
30199
+ function curve_hits(path, boxes, box_s, box_t) {
30200
+ let pts = path_points(path);
30201
+ if (pts.length < 2) return false;
30202
+ let bb = {
30203
+ x1: Infinity,
30204
+ y1: Infinity,
30205
+ x2: -Infinity,
30206
+ y2: -Infinity
30207
+ };
30208
+ for (let p of pts) {
30209
+ bb.x1 = Math.min(bb.x1, p[0]);
30210
+ bb.y1 = Math.min(bb.y1, p[1]);
30211
+ bb.x2 = Math.max(bb.x2, p[0]);
30212
+ bb.y2 = Math.max(bb.y2, p[1]);
30213
+ }
30214
+ for (let b of boxes || []) {
30215
+ if (same_box(b, box_s) || same_box(b, box_t) || !box_overlaps(b, box_expand(bb, 1))) continue;
30216
+ let inner = box_expand(b, -1);
30217
+ for (let k = 0; k + 1 < pts.length; k++) if (segment_hits(pts[k], pts[k + 1], inner)) return true;
30218
+ }
30219
+ return false;
30220
+ }
30221
+ /************************************************************
30222
+ * A G6 path (['M'|'L'|'Q', ...]) along `points` with every corner
30223
+ * rounded -- by `radius`, or by half the shorter of its two
30224
+ * segments, so a short run is never overshot. The rounding is the
30225
+ * quadratic G6's own polyline uses.
30226
+ ************************************************************/
30227
+ function rounded_path(points, radius) {
30228
+ let path = [[
30229
+ "M",
30230
+ points[0][0],
30231
+ points[0][1]
30232
+ ]];
30233
+ for (let i = 1; i + 1 < points.length; i++) {
30234
+ let a = points[i - 1];
30235
+ let m = points[i];
30236
+ let b = points[i + 1];
30237
+ let len_in = Math.hypot(m[0] - a[0], m[1] - a[1]);
30238
+ let len_out = Math.hypot(b[0] - m[0], b[1] - m[1]);
30239
+ let r = Math.min(radius, len_in / 2, len_out / 2);
30240
+ let collinear = Math.abs((m[0] - a[0]) * (b[1] - m[1]) - (m[1] - a[1]) * (b[0] - m[0])) < 1e-9;
30241
+ if (r <= 0 || collinear) {
30242
+ path.push([
30243
+ "L",
30244
+ m[0],
30245
+ m[1]
30246
+ ]);
30247
+ continue;
30248
+ }
30249
+ let p1 = [m[0] - (m[0] - a[0]) / len_in * r, m[1] - (m[1] - a[1]) / len_in * r];
30250
+ let p2 = [m[0] + (b[0] - m[0]) / len_out * r, m[1] + (b[1] - m[1]) / len_out * r];
30251
+ path.push([
30252
+ "L",
30253
+ p1[0],
30254
+ p1[1]
30255
+ ]);
30256
+ path.push([
30257
+ "Q",
30258
+ m[0],
30259
+ m[1],
30260
+ p2[0],
30261
+ p2[1]
30262
+ ]);
30263
+ }
30264
+ let last = points[points.length - 1];
30265
+ path.push([
30266
+ "L",
30267
+ last[0],
30268
+ last[1]
30269
+ ]);
30270
+ return path;
30271
+ }
30166
30272
  //#endregion
30167
30273
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
30168
30274
  function _typeof(o) {
@@ -55050,7 +55156,7 @@ Easing Functions from anime.js, they are tried and true, so, its better to use t
55050
55156
  var Quad = function Quad(t) {
55051
55157
  return Math.pow(t, 2);
55052
55158
  };
55053
- var Cubic = function Cubic(t) {
55159
+ var Cubic$1 = function Cubic(t) {
55054
55160
  return Math.pow(t, 3);
55055
55161
  };
55056
55162
  var Quart = function Quart(t) {
@@ -55187,10 +55293,10 @@ var EasingFunctions = {
55187
55293
  "out-quad": EaseOut(Quad),
55188
55294
  "in-out-quad": EaseInOut(Quad),
55189
55295
  "out-in-quad": EaseOutIn(Quad),
55190
- "in-cubic": Cubic,
55191
- "out-cubic": EaseOut(Cubic),
55192
- "in-out-cubic": EaseInOut(Cubic),
55193
- "out-in-cubic": EaseOutIn(Cubic),
55296
+ "in-cubic": Cubic$1,
55297
+ "out-cubic": EaseOut(Cubic$1),
55298
+ "in-out-cubic": EaseInOut(Cubic$1),
55299
+ "out-in-cubic": EaseOutIn(Cubic$1),
55194
55300
  "in-quart": Quart,
55195
55301
  "out-quart": EaseOut(Quart),
55196
55302
  "in-out-quart": EaseInOut(Quart),
@@ -57363,6 +57469,8 @@ function register_layouts(gobj) {
57363
57469
  register(ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
57364
57470
  register(ExtensionCategory.EDGE, "treedb-elbow-v", TreedbElbowV);
57365
57471
  register(ExtensionCategory.EDGE, "treedb-elbow-h", TreedbElbowH);
57472
+ register(ExtensionCategory.EDGE, "treedb-curve-v", TreedbCurveV);
57473
+ register(ExtensionCategory.EDGE, "treedb-curve-h", TreedbCurveH);
57366
57474
  register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
57367
57475
  register(ExtensionCategory.NODE, "light", LightNode);
57368
57476
  register(ExtensionCategory.NODE, "treedb-card", TreedbCard);
@@ -58057,7 +58165,9 @@ function layout_direction(gobj) {
58057
58165
  function edge_type_for(gobj) {
58058
58166
  let lr = layout_direction(gobj) === "LR";
58059
58167
  let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
58060
- if (gobj_read_str_attr(gobj, "edge_shape") === "elbow" && !str_in_list(rowless, gobj.priv.layout)) return lr ? "treedb-elbow-h" : "treedb-elbow-v";
58168
+ let rows = !str_in_list(rowless, gobj.priv.layout);
58169
+ if (rows && gobj_read_str_attr(gobj, "edge_shape") === "elbow") return lr ? "treedb-elbow-h" : "treedb-elbow-v";
58170
+ if (rows) return lr ? "treedb-curve-h" : "treedb-curve-v";
58061
58171
  return lr ? "cubic-horizontal" : "cubic";
58062
58172
  }
58063
58173
  /************************************************************
@@ -59661,6 +59771,40 @@ var TreedbElbowH = class extends Polyline {
59661
59771
  return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59662
59772
  }
59663
59773
  };
59774
+ /************************************************************
59775
+ * A CURVED edge that keeps off the cards. G6's cubic, as before,
59776
+ * unless that very curve -- sampled, not guessed from the
59777
+ * rectangle round it -- would cross a card other than its own two,
59778
+ * or when it does not run forward and so would cross its own:
59779
+ * the card above a lower row of a stack, a row between a parent
59780
+ * and a far child. Then it goes the way an elbow would (the comb,
59781
+ * the detour, the route: the same lanes and staggered turns) with
59782
+ * round corners, so it still reads as a curve. `-v` for rows
59783
+ * stacked top to bottom, `-h` for columns.
59784
+ ************************************************************/
59785
+ function curve_key_path(edge, attributes, vertical, plain) {
59786
+ let [s, t] = edge.getEndpoints(attributes, false);
59787
+ let along = vertical ? 1 : 0;
59788
+ let box_s = elbow_box(edge.sourceNode);
59789
+ let box_t = elbow_box(edge.targetNode);
59790
+ let boxes = elbow_obstacles(edge);
59791
+ if (t[along] - s[along] > 20 && !curve_hits(plain, boxes, box_s, box_t)) return plain;
59792
+ return rounded_path([
59793
+ s,
59794
+ ...elbow_route(s, t, box_s, box_t, elbow_lane_of(edge), vertical, boxes, elbow_stagger_of(edge, vertical)),
59795
+ t
59796
+ ], 24);
59797
+ }
59798
+ var TreedbCurveV = class extends Cubic {
59799
+ getKeyPath(attributes) {
59800
+ return curve_key_path(this, attributes, true, super.getKeyPath(attributes));
59801
+ }
59802
+ };
59803
+ var TreedbCurveH = class extends CubicHorizontal {
59804
+ getKeyPath(attributes) {
59805
+ return curve_key_path(this, attributes, false, super.getKeyPath(attributes));
59806
+ }
59807
+ };
59664
59808
  var TreedbCompactLayout = class extends BaseLayout {
59665
59809
  async execute(data, options) {
59666
59810
  let input = treedb_layout_input(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.154",
3
+ "version": "7.23.155",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -133,7 +133,10 @@ import {
133
133
  fold_root_group_key,
134
134
  } from "./treedb_fold_model.js";
135
135
  import {layout_tree, layout_radial, layout_compact} from "./treedb_layout.js";
136
- import {elbow_lane, elbow_rank, elbow_route, elbow_combs} from "./treedb_elbow.js";
136
+ import {
137
+ elbow_lane, elbow_rank, elbow_route, elbow_combs, curve_hits, rounded_path, CURVE_RADIUS,
138
+ ELBOW_STEP,
139
+ } from "./treedb_elbow.js";
137
140
 
138
141
  import {
139
142
  BaseLayout,
@@ -148,6 +151,8 @@ import {
148
151
  Toolbar,
149
152
  register,
150
153
  Polyline,
154
+ Cubic,
155
+ CubicHorizontal,
151
156
  } from '@antv/g6';
152
157
 
153
158
  import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
@@ -942,6 +947,8 @@ function register_layouts(gobj)
942
947
  register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
943
948
  register(ExtensionCategory.EDGE, 'treedb-elbow-v', TreedbElbowV);
944
949
  register(ExtensionCategory.EDGE, 'treedb-elbow-h', TreedbElbowH);
950
+ register(ExtensionCategory.EDGE, 'treedb-curve-v', TreedbCurveV);
951
+ register(ExtensionCategory.EDGE, 'treedb-curve-h', TreedbCurveH);
945
952
  register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
946
953
  register(ExtensionCategory.NODE, 'light', LightNode);
947
954
  register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
@@ -2042,16 +2049,20 @@ function layout_direction(gobj)
2042
2049
  return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
2043
2050
  }
2044
2051
 
2045
- /* A curve, or an elbow when the reader asked for one and the layout
2046
- * has rows to run it between. */
2052
+ /* An elbow when the reader asked for one, a curve otherwise -- ours
2053
+ * where the layout has rows, which keeps off the cards; G6's plain
2054
+ * cubic on a layout with no rows to go round by. */
2047
2055
  function edge_type_for(gobj)
2048
2056
  {
2049
2057
  let lr = (layout_direction(gobj) === "LR");
2050
2058
  let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
2051
- if(gobj_read_str_attr(gobj, "edge_shape") === "elbow" &&
2052
- !str_in_list(rowless, gobj.priv.layout)) {
2059
+ let rows = !str_in_list(rowless, gobj.priv.layout);
2060
+ if(rows && gobj_read_str_attr(gobj, "edge_shape") === "elbow") {
2053
2061
  return lr? "treedb-elbow-h" : "treedb-elbow-v";
2054
2062
  }
2063
+ if(rows) {
2064
+ return lr? "treedb-curve-h" : "treedb-curve-v";
2065
+ }
2055
2066
  return lr? "cubic-horizontal" : "cubic";
2056
2067
  }
2057
2068
 
@@ -4388,6 +4399,51 @@ class TreedbElbowH extends Polyline
4388
4399
  }
4389
4400
  }
4390
4401
 
4402
+ /************************************************************
4403
+ * A CURVED edge that keeps off the cards. G6's cubic, as before,
4404
+ * unless that very curve -- sampled, not guessed from the
4405
+ * rectangle round it -- would cross a card other than its own two,
4406
+ * or when it does not run forward and so would cross its own:
4407
+ * the card above a lower row of a stack, a row between a parent
4408
+ * and a far child. Then it goes the way an elbow would (the comb,
4409
+ * the detour, the route: the same lanes and staggered turns) with
4410
+ * round corners, so it still reads as a curve. `-v` for rows
4411
+ * stacked top to bottom, `-h` for columns.
4412
+ ************************************************************/
4413
+ function curve_key_path(edge, attributes, vertical, plain)
4414
+ {
4415
+ let [s, t] = edge.getEndpoints(attributes, false);
4416
+ let along = vertical? 1 : 0;
4417
+ let box_s = elbow_box(edge.sourceNode);
4418
+ let box_t = elbow_box(edge.targetNode);
4419
+ let boxes = elbow_obstacles(edge);
4420
+ /* An edge that does not run forward -- one of every reciprocal
4421
+ * pair, an edge within one row -- runs through its OWN two cards
4422
+ * as a cubic, which curve_hits does not look at: it goes round
4423
+ * them, as its elbow does. */
4424
+ let forward = (t[along] - s[along] > 2 * ELBOW_STEP);
4425
+ if(forward && !curve_hits(plain, boxes, box_s, box_t)) {
4426
+ return plain;
4427
+ }
4428
+ let pts = elbow_route(s, t, box_s, box_t, elbow_lane_of(edge), vertical, boxes,
4429
+ elbow_stagger_of(edge, vertical));
4430
+ return rounded_path([s, ...pts, t], CURVE_RADIUS);
4431
+ }
4432
+
4433
+ class TreedbCurveV extends Cubic
4434
+ {
4435
+ getKeyPath(attributes) {
4436
+ return curve_key_path(this, attributes, true, super.getKeyPath(attributes));
4437
+ }
4438
+ }
4439
+
4440
+ class TreedbCurveH extends CubicHorizontal
4441
+ {
4442
+ getKeyPath(attributes) {
4443
+ return curve_key_path(this, attributes, false, super.getKeyPath(attributes));
4444
+ }
4445
+ }
4446
+
4391
4447
  class TreedbCompactLayout extends BaseLayout
4392
4448
  {
4393
4449
  async execute(data, options) {
@@ -40,6 +40,10 @@
40
40
  * channel, so its edge runs down the corridor in a lane of
41
41
  * its own -- a COMB -- and turns into the gap above its card.
42
42
  *
43
+ * And for CURVED edges (curve_hits, rounded_path): a curve
44
+ * that would cross another card follows the same way an elbow
45
+ * would, with round corners, so it reads as a curve.
46
+ *
43
47
  * Written for the top-down reading; left to right is the same
44
48
  * geometry with the axes swapped.
45
49
  *
@@ -609,3 +613,112 @@ export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger)
609
613
  }
610
614
  return route || simple;
611
615
  }
616
+
617
+ /*----------------------------------------------------------------*
618
+ * CURVED edges that would cross a card
619
+ *----------------------------------------------------------------*/
620
+ export const CURVE_RADIUS = 24; /* the corners of a curve that goes round */
621
+ export const CURVE_STEPS = 16; /* samples of each curved stretch */
622
+
623
+ /* Points along a G6 path: the ends of its runs and CURVE_STEPS
624
+ * samples of each quadratic or cubic stretch. */
625
+ function path_points(path)
626
+ {
627
+ let pts = [];
628
+ let cur = null;
629
+ for(let c of path) {
630
+ if(c[0] === 'M' || c[0] === 'L') {
631
+ cur = [c[1], c[2]];
632
+ pts.push(cur);
633
+ } else if(c[0] === 'C' && cur) {
634
+ let [x0, y0] = cur;
635
+ for(let k = 1; k <= CURVE_STEPS; k++) {
636
+ let u = k / CURVE_STEPS;
637
+ let v = 1 - u;
638
+ pts.push([
639
+ v * v * v * x0 + 3 * v * v * u * c[1] + 3 * v * u * u * c[3] + u * u * u * c[5],
640
+ v * v * v * y0 + 3 * v * v * u * c[2] + 3 * v * u * u * c[4] + u * u * u * c[6],
641
+ ]);
642
+ }
643
+ cur = [c[5], c[6]];
644
+ } else if(c[0] === 'Q' && cur) {
645
+ let [x0, y0] = cur;
646
+ for(let k = 1; k <= CURVE_STEPS; k++) {
647
+ let u = k / CURVE_STEPS;
648
+ let v = 1 - u;
649
+ pts.push([
650
+ v * v * x0 + 2 * v * u * c[1] + u * u * c[3],
651
+ v * v * y0 + 2 * v * u * c[2] + u * u * c[4],
652
+ ]);
653
+ }
654
+ cur = [c[3], c[4]];
655
+ }
656
+ }
657
+ return pts;
658
+ }
659
+
660
+ /************************************************************
661
+ * Does the G6 path `path` of a curved edge cross a card other than
662
+ * its own two? Measured on the curve itself -- the path G6 would
663
+ * draw, sampled -- and not on the rectangle round it, which took
664
+ * every card the curve merely passed near: the ports of a row
665
+ * stick well into the space above it. Its own two cards are left
666
+ * out: the curve starts and ends on their ports.
667
+ ************************************************************/
668
+ export function curve_hits(path, boxes, box_s, box_t)
669
+ {
670
+ let pts = path_points(path);
671
+ if(pts.length < 2) {
672
+ return false;
673
+ }
674
+ let bb = {x1: Infinity, y1: Infinity, x2: -Infinity, y2: -Infinity};
675
+ for(let p of pts) {
676
+ bb.x1 = Math.min(bb.x1, p[0]);
677
+ bb.y1 = Math.min(bb.y1, p[1]);
678
+ bb.x2 = Math.max(bb.x2, p[0]);
679
+ bb.y2 = Math.max(bb.y2, p[1]);
680
+ }
681
+ for(let b of (boxes || [])) {
682
+ if(same_box(b, box_s) || same_box(b, box_t) || !box_overlaps(b, box_expand(bb, 1))) {
683
+ continue;
684
+ }
685
+ let inner = box_expand(b, -1);
686
+ for(let k = 0; k + 1 < pts.length; k++) {
687
+ if(segment_hits(pts[k], pts[k + 1], inner)) {
688
+ return true;
689
+ }
690
+ }
691
+ }
692
+ return false;
693
+ }
694
+
695
+ /************************************************************
696
+ * A G6 path (['M'|'L'|'Q', ...]) along `points` with every corner
697
+ * rounded -- by `radius`, or by half the shorter of its two
698
+ * segments, so a short run is never overshot. The rounding is the
699
+ * quadratic G6's own polyline uses.
700
+ ************************************************************/
701
+ export function rounded_path(points, radius)
702
+ {
703
+ let path = [['M', points[0][0], points[0][1]]];
704
+ for(let i = 1; i + 1 < points.length; i++) {
705
+ let a = points[i - 1];
706
+ let m = points[i];
707
+ let b = points[i + 1];
708
+ let len_in = Math.hypot(m[0] - a[0], m[1] - a[1]);
709
+ let len_out = Math.hypot(b[0] - m[0], b[1] - m[1]);
710
+ let r = Math.min(radius, len_in / 2, len_out / 2);
711
+ let collinear = Math.abs((m[0] - a[0]) * (b[1] - m[1]) - (m[1] - a[1]) * (b[0] - m[0])) < 1e-9;
712
+ if(r <= 0 || collinear) {
713
+ path.push(['L', m[0], m[1]]);
714
+ continue;
715
+ }
716
+ let p1 = [m[0] - (m[0] - a[0]) / len_in * r, m[1] - (m[1] - a[1]) / len_in * r];
717
+ let p2 = [m[0] + (b[0] - m[0]) / len_out * r, m[1] + (b[1] - m[1]) / len_out * r];
718
+ path.push(['L', p1[0], p1[1]]);
719
+ path.push(['Q', m[0], m[1], p2[0], p2[1]]);
720
+ }
721
+ let last = points[points.length - 1];
722
+ path.push(['L', last[0], last[1]]);
723
+ return path;
724
+ }
@@ -8,7 +8,7 @@
8
8
  import { describe, test, expect } from "vitest";
9
9
  import {
10
10
  elbow_lane, elbow_points, elbow_route, elbow_path_hits, elbow_rank, elbow_combs,
11
- ELBOW_STEP, ELBOW_CLEAR,
11
+ curve_hits, rounded_path, ELBOW_STEP, ELBOW_CLEAR,
12
12
  } from "./treedb_elbow.js";
13
13
  import { layout_compact } from "./treedb_layout.js";
14
14
 
@@ -356,6 +356,50 @@ describe("the comb of a stack", () => {
356
356
  });
357
357
  });
358
358
 
359
+ describe("a curve that would cross a card", () => {
360
+ /* Whole-node boxes (ports 7px out of top and bottom). */
361
+ const PB = {x1: 0, y1: -7, x2: 172, y2: 103}; /* a parent */
362
+ const R0 = {x1: 0, y1: 183, x2: 116, y2: 237}; /* row 0 of a stack */
363
+ const R1 = {x1: 0, y1: 277, x2: 116, y2: 331}; /* row 1, under it */
364
+ const SIB = {x1: 200, y1: -7, x2: 372, y2: 103}; /* the parent's sibling */
365
+ const S = [86, 96];
366
+
367
+ /* The cubic G6 draws between two vertical ports: tangents along
368
+ * the line, as its default curveOffset gives. */
369
+ const cubic = (s, t) => [["M", s[0], s[1]],
370
+ ["C", s[0], (s[1] + t[1]) / 2, t[0], (s[1] + t[1]) / 2, t[0], t[1]]];
371
+
372
+ test("a curve into a card of the next row crosses nothing", () => {
373
+ expect(curve_hits(cubic(S, [58, 190]), [PB, R0, R1, SIB], PB, R0)).toBe(false);
374
+ });
375
+
376
+ test("a curve into a lower row of a stack crosses the card above it", () => {
377
+ expect(curve_hits(cubic(S, [58, 284]), [PB, R0, R1, SIB], PB, R1)).toBe(true);
378
+ });
379
+
380
+ test("a curve that passes NEAR a card, beside its ports, does not count", () => {
381
+ /* N0 in the target row with its port poking 7px up, beside
382
+ * where the curve lands; the rectangle round the curve took it. */
383
+ const T0 = {x1: 250, y1: 183, x2: 366, y2: 237};
384
+ const N0 = {x1: 120, y1: 183, x2: 236, y2: 237};
385
+ expect(curve_hits(cubic(S, [308, 190]), [PB, SIB, T0, N0], PB, T0)).toBe(false);
386
+ });
387
+
388
+ test("the rounded path: a straight line is one run", () => {
389
+ expect(rounded_path([[0, 0], [0, 100]], 24)).toEqual([["M", 0, 0], ["L", 0, 100]]);
390
+ });
391
+
392
+ test("the rounded path: each corner a quadratic, never past half a short run", () => {
393
+ let path = rounded_path([[0, 0], [0, 60], [100, 60], [100, 70]], 24);
394
+ expect(path[0]).toEqual(["M", 0, 0]);
395
+ expect(path[1]).toEqual(["L", 0, 36]); /* 24 before the corner */
396
+ expect(path[2]).toEqual(["Q", 0, 60, 24, 60]);
397
+ expect(path[3]).toEqual(["L", 95, 60]); /* the last run is 10: r = 5 */
398
+ expect(path[4]).toEqual(["Q", 100, 60, 100, 65]);
399
+ expect(path[5]).toEqual(["L", 100, 70]);
400
+ });
401
+ });
402
+
359
403
  describe("the ports are obstacles too", () => {
360
404
  /* Whole-node boxes: a port of 14px sticks 7px out of the bottom
361
405
  * and the top of each card. */