@yuneta/gobj-ui 7.23.152 → 7.23.154

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.
@@ -29326,6 +29326,14 @@ function tidy_tree_lr(nodes, edges, o) {
29326
29326
  * the packing across the depths differs, so switching between the
29327
29327
  * two moves cards sideways and never reorders them.
29328
29328
  *
29329
+ * And a run of three or more LEAF children -- a hall's devices, a
29330
+ * region's places when none is open -- is STACKED: two columns
29331
+ * under the parent with a corridor between them, row after row.
29332
+ * That is what makes it compact when a whole level is open, where
29333
+ * the contour alone gains nothing: every parent needed the whole
29334
+ * row of its children beside it. The tree grows taller instead of
29335
+ * wider. Its elbow edges run down the corridor (elbow_combs).
29336
+ *
29329
29337
  * The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
29330
29338
  * Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
29331
29339
  * -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
@@ -29406,10 +29414,81 @@ function contour_merge(p1, p2) {
29406
29414
  p1.lower_head = p2.lower_head;
29407
29415
  return total;
29408
29416
  }
29417
+ function stack_trunk_width(k) {
29418
+ let lanes = Math.max(0, k - 2);
29419
+ return Math.max(36, 20 + Math.max(0, lanes - 1) * 6);
29420
+ }
29421
+ function stack_geometry(t, st, col_x, col_w) {
29422
+ let d = st.depth;
29423
+ let k = st.ids.length;
29424
+ let rows = Math.ceil(k / 2);
29425
+ let side = [0, 0];
29426
+ let row_len = [];
29427
+ for (let i = 0; i < k; i++) {
29428
+ let n = t.by_id.get(st.ids[i]);
29429
+ let r = Math.floor(i / 2);
29430
+ side[i % 2] = Math.max(side[i % 2], n.h);
29431
+ row_len[r] = Math.max(row_len[r] || 0, n.w);
29432
+ }
29433
+ row_len[0] = col_w[d];
29434
+ let trunk = stack_trunk_width(k);
29435
+ let starts = [col_x[d]];
29436
+ for (let r = 1; r < rows; r++) starts[r] = starts[r - 1] + row_len[r - 1] + 40;
29437
+ return {
29438
+ side,
29439
+ trunk,
29440
+ starts,
29441
+ row_len,
29442
+ len: starts[rows - 1] + row_len[rows - 1] - col_x[d],
29443
+ ext: side[0] + trunk + side[1]
29444
+ };
29445
+ }
29446
+ function place_stack(t, st, g, top, pos) {
29447
+ for (let i = 0; i < st.ids.length; i++) {
29448
+ let n = t.by_id.get(st.ids[i]);
29449
+ let r = Math.floor(i / 2);
29450
+ let across = i % 2 === 0 ? top + g.side[0] - n.h / 2 : top + g.side[0] + g.trunk + n.h / 2;
29451
+ pos.set(st.ids[i], {
29452
+ x: g.starts[r] + g.row_len[r] / 2,
29453
+ y: across
29454
+ });
29455
+ }
29456
+ }
29409
29457
  function compact_tree_lr(nodes, edges, o) {
29410
29458
  let t = spanning_tree(nodes, edges);
29411
- let order = walk_order(t);
29412
29459
  let pos = /* @__PURE__ */ new Map();
29460
+ let kids_of = /* @__PURE__ */ new Map();
29461
+ let stacks = /* @__PURE__ */ new Map();
29462
+ for (let [id, kids] of t.children) {
29463
+ let out = [];
29464
+ let run = [];
29465
+ let flush = () => {
29466
+ if (run.length >= 3) {
29467
+ let key = { stack: stacks.size };
29468
+ stacks.set(key, {
29469
+ ids: run,
29470
+ depth: t.depth.get(run[0])
29471
+ });
29472
+ out.push(key);
29473
+ } else out.push(...run);
29474
+ run = [];
29475
+ };
29476
+ for (let kid of kids) if ((t.children.get(kid) || []).length === 0) run.push(kid);
29477
+ else {
29478
+ flush();
29479
+ out.push(kid);
29480
+ }
29481
+ flush();
29482
+ kids_of.set(id, out);
29483
+ }
29484
+ let order = [];
29485
+ let todo = t.roots.slice().reverse();
29486
+ while (todo.length) {
29487
+ let id = todo.pop();
29488
+ order.push(id);
29489
+ let kids = kids_of.get(id) || [];
29490
+ for (let i = kids.length - 1; i >= 0; i--) todo.push(kids[i]);
29491
+ }
29413
29492
  let col_w = [];
29414
29493
  for (let [id, d] of t.depth) col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
29415
29494
  let col_x = [];
@@ -29422,18 +29501,32 @@ function compact_tree_lr(nodes, edges, o) {
29422
29501
  let dist = o.nodesep;
29423
29502
  let level_distance = o.ranksep - pad;
29424
29503
  let tn = /* @__PURE__ */ new Map();
29425
- for (let id of order) tn.set(id, {
29426
- len: col_w[t.depth.get(id)],
29427
- ext: t.by_id.get(id).h,
29428
- off_x: 0,
29429
- off_y: 0,
29430
- y: 0,
29431
- contour: null
29432
- });
29504
+ for (let id of order) {
29505
+ let st = stacks.get(id);
29506
+ if (st) {
29507
+ let g = stack_geometry(t, st, col_x, col_w);
29508
+ tn.set(id, {
29509
+ len: g.len,
29510
+ ext: g.ext,
29511
+ geom: g,
29512
+ off_x: 0,
29513
+ off_y: 0,
29514
+ y: 0,
29515
+ contour: null
29516
+ });
29517
+ } else tn.set(id, {
29518
+ len: col_w[t.depth.get(id)],
29519
+ ext: t.by_id.get(id).h,
29520
+ off_x: 0,
29521
+ off_y: 0,
29522
+ y: 0,
29523
+ contour: null
29524
+ });
29525
+ }
29433
29526
  for (let i = order.length - 1; i >= 0; i--) {
29434
29527
  let id = order[i];
29435
29528
  let n = tn.get(id);
29436
- let kids = t.children.get(id) || [];
29529
+ let kids = kids_of.get(id) || [];
29437
29530
  if (!kids.length) {
29438
29531
  let upper = contour_line(n.len + dist, 0);
29439
29532
  let lower_tail = contour_line(0, -n.ext - dist);
@@ -29469,7 +29562,7 @@ function compact_tree_lr(nodes, edges, o) {
29469
29562
  for (let id of order) {
29470
29563
  let n = tn.get(id);
29471
29564
  if (!root_of.has(id)) root_of.set(id, id);
29472
- let kids = t.children.get(id) || [];
29565
+ let kids = kids_of.get(id) || [];
29473
29566
  if (!kids.length) continue;
29474
29567
  let running = n.y + tn.get(kids[0]).off_y;
29475
29568
  tn.get(kids[0]).y = running;
@@ -29496,9 +29589,14 @@ function compact_tree_lr(nodes, edges, o) {
29496
29589
  top += hi.get(r) - lo.get(r) + o.nodesep * 2;
29497
29590
  }
29498
29591
  for (let id of order) {
29499
- let d = t.depth.get(id);
29500
29592
  let n = tn.get(id);
29501
29593
  let y = n.y + shift.get(root_of.get(id));
29594
+ let st = stacks.get(id);
29595
+ if (st) {
29596
+ place_stack(t, st, n.geom, y, pos);
29597
+ continue;
29598
+ }
29599
+ let d = t.depth.get(id);
29502
29600
  pos.set(id, {
29503
29601
  x: col_x[d] + col_w[d] / 2,
29504
29602
  y: y + n.ext / 2
@@ -29635,6 +29733,23 @@ function elbow_lane(ids, id) {
29635
29733
  if (k <= 0) return 0;
29636
29734
  return (k % 2 ? 1 : -1) * Math.ceil(k / 2);
29637
29735
  }
29736
+ /************************************************************
29737
+ * The rank of `id` among `items` ([{id, span}]): the forward
29738
+ * edges that share one end with it, each with how far it runs
29739
+ * ACROSS. Farthest first; a tie goes to the edge made first.
29740
+ * Returns {rank, count}.
29741
+ ************************************************************/
29742
+ function elbow_rank(items, id) {
29743
+ let sorted = items.slice().sort((a, b) => {
29744
+ if (b.span !== a.span) return b.span - a.span;
29745
+ return String(a.id).localeCompare(String(b.id), void 0, { numeric: true });
29746
+ });
29747
+ let rank = sorted.findIndex((it) => it.id === id);
29748
+ return {
29749
+ rank: rank < 0 ? 0 : rank,
29750
+ count: sorted.length
29751
+ };
29752
+ }
29638
29753
  function swap(p) {
29639
29754
  return [p[1], p[0]];
29640
29755
  }
@@ -29646,6 +29761,136 @@ function swap_box(b) {
29646
29761
  y2: b.x2
29647
29762
  };
29648
29763
  }
29764
+ function staggered_turn(lo, hi, st) {
29765
+ let mid = (lo + hi) / 2;
29766
+ let group = null;
29767
+ let k = 0;
29768
+ if (st.dep && st.dep.count > 1) {
29769
+ group = st.dep;
29770
+ k = st.dep.rank;
29771
+ } else if (st.arr && st.arr.count > 1) {
29772
+ group = st.arr;
29773
+ k = st.arr.count - 1 - st.arr.rank;
29774
+ }
29775
+ if (group) {
29776
+ let room = Math.max(0, hi - lo - 12);
29777
+ let step = Math.min(10, room / (group.count - 1));
29778
+ mid = mid - (group.count - 1) * step / 2 + k * step;
29779
+ }
29780
+ return mid;
29781
+ }
29782
+ /************************************************************
29783
+ * The combs of the stacks under one card.
29784
+ *
29785
+ * treedb_layout.js lays a run of leaf children out as two columns
29786
+ * with a corridor between them. A card in the first row is reached
29787
+ * from the channel like any other; one in a lower row cannot be --
29788
+ * the card above it stands in the way -- so its edge runs down
29789
+ * the corridor in a LANE of its own and turns into the gap above
29790
+ * its card, which it shares only with the other card of its row,
29791
+ * reached from the other side.
29792
+ *
29793
+ * targets [{id, box}]: the forward edges leaving one card,
29794
+ * each with the box of the card it reaches
29795
+ * vertical as in elbow_points; false swaps the boxes
29796
+ *
29797
+ * Returns Map id -> {lane, z, top}: the lane, the height of the
29798
+ * gap it turns into, and the top of the stack's first row (where
29799
+ * the channel over the stack ends). Only for a card that stands
29800
+ * under another of the group, within a stack gap and lined up
29801
+ * with it on the corridor side -- what a stack looks like.
29802
+ *
29803
+ * Lanes go outermost first, row by row: a line into a higher row
29804
+ * keeps outside the lines going deeper, and none crosses another.
29805
+ ************************************************************/
29806
+ function elbow_combs(targets, vertical) {
29807
+ let list = vertical ? targets : targets.map((it) => ({
29808
+ id: it.id,
29809
+ box: swap_box(it.box)
29810
+ }));
29811
+ let cy = (b) => (b.y1 + b.y2) / 2;
29812
+ let near = (a, b) => Math.abs(a - b) < 1.5;
29813
+ let under = [];
29814
+ for (let it of list) {
29815
+ let b = it.box;
29816
+ let above = null;
29817
+ for (let o of list) {
29818
+ let a = o.box;
29819
+ if (o === it || a.y2 > b.y1 || b.y1 - a.y2 > 40) continue;
29820
+ if (!(near(a.x2, b.x2) || near(a.x1, b.x1))) continue;
29821
+ if (!above || a.y2 > above.y2) above = a;
29822
+ }
29823
+ if (!above) continue;
29824
+ let side;
29825
+ if (near(above.x2, b.x2) && !near(above.x1, b.x1)) side = 0;
29826
+ else if (near(above.x1, b.x1) && !near(above.x2, b.x2)) side = 1;
29827
+ else side = list.some((o) => o !== it && near(cy(o.box), cy(b)) && o.box.x2 <= b.x1) ? 1 : 0;
29828
+ under.push({
29829
+ id: it.id,
29830
+ box: b,
29831
+ above,
29832
+ side
29833
+ });
29834
+ }
29835
+ let corridor = (u) => {
29836
+ if (u.side === 0) {
29837
+ let l = u.box.x2;
29838
+ let col = list.filter((o) => near(o.box.x2, l));
29839
+ let r = Infinity;
29840
+ for (let c of col) for (let o of list) if (o !== c && near(cy(o.box), cy(c.box)) && o.box.x1 >= l - .5) r = Math.min(r, o.box.x1);
29841
+ return {
29842
+ l,
29843
+ r: isFinite(r) ? r : l + 36,
29844
+ col
29845
+ };
29846
+ }
29847
+ let r = u.box.x1;
29848
+ let col = list.filter((o) => near(o.box.x1, r));
29849
+ let l = -Infinity;
29850
+ for (let c of col) for (let o of list) if (o !== c && near(cy(o.box), cy(c.box)) && o.box.x2 <= r + .5) l = Math.max(l, o.box.x2);
29851
+ return {
29852
+ l: isFinite(l) ? l : r - 36,
29853
+ r,
29854
+ col
29855
+ };
29856
+ };
29857
+ let groups = /* @__PURE__ */ new Map();
29858
+ for (let u of under) {
29859
+ let c = corridor(u);
29860
+ u.top = Math.min(...c.col.map((o) => o.box.y1));
29861
+ let key = `${Math.round(c.l)}|${Math.round(c.r)}`;
29862
+ if (!groups.has(key)) groups.set(key, {
29863
+ l: c.l,
29864
+ r: c.r,
29865
+ left: [],
29866
+ right: []
29867
+ });
29868
+ groups.get(key)[u.side === 0 ? "left" : "right"].push(u);
29869
+ }
29870
+ let out = /* @__PURE__ */ new Map();
29871
+ for (let g of groups.values()) {
29872
+ g.left.sort((a, b) => a.box.y1 - b.box.y1);
29873
+ g.right.sort((a, b) => a.box.y1 - b.box.y1);
29874
+ let n = g.left.length + g.right.length;
29875
+ let room = g.r - g.l - 20;
29876
+ let step = n > 1 ? Math.max(0, Math.min(6, room / (n - 1))) : 0;
29877
+ g.left.forEach((u, i) => {
29878
+ out.set(u.id, {
29879
+ lane: g.l + 10 + i * step,
29880
+ z: (u.above.y2 + u.box.y1) / 2,
29881
+ top: u.top
29882
+ });
29883
+ });
29884
+ g.right.forEach((u, j) => {
29885
+ out.set(u.id, {
29886
+ lane: g.r - 10 - j * step,
29887
+ z: (u.above.y2 + u.box.y1) / 2,
29888
+ top: u.top
29889
+ });
29890
+ });
29891
+ }
29892
+ return out;
29893
+ }
29649
29894
  /************************************************************
29650
29895
  * The control points of an elbow from the port `s` to the port
29651
29896
  * `t` ([x, y] each), knowing the boxes of the two cards
@@ -29653,13 +29898,33 @@ function swap_box(b) {
29653
29898
  *
29654
29899
  * vertical the rows are stacked (top to bottom); false
29655
29900
  * when they are side by side (left to right)
29656
- ************************************************************/
29657
- function elbow_points(s, t, box_s, box_t, lane, vertical) {
29658
- if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true).map(swap);
29901
+ * stagger {dep, arr, comb}: the edge's rank among the
29902
+ * forward edges leaving its source card and among
29903
+ * those reaching its target card (elbow_rank), and
29904
+ * its comb when the target is in a lower row of a
29905
+ * stack (elbow_combs). Absent, the channel is the
29906
+ * middle.
29907
+ ************************************************************/
29908
+ function elbow_points(s, t, box_s, box_t, lane, vertical, stagger) {
29909
+ if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, stagger).map(swap);
29659
29910
  let shift = lane * 10;
29660
29911
  if (t[1] - s[1] > 20) {
29661
- let mid = (s[1] + t[1]) / 2 + shift;
29662
- mid = Math.min(Math.max(mid, s[1] + 10), t[1] - 10);
29912
+ let st = stagger || {};
29913
+ let lo = Math.max(s[1], box_s.y2);
29914
+ if (st.comb) {
29915
+ let c = st.comb;
29916
+ let turn = staggered_turn(lo, c.top, { dep: st.dep }) + shift;
29917
+ turn = Math.min(Math.max(turn, lo + 4), c.top - 4);
29918
+ return [
29919
+ [s[0], turn],
29920
+ [c.lane, turn],
29921
+ [c.lane, c.z],
29922
+ [t[0], c.z]
29923
+ ];
29924
+ }
29925
+ let hi = Math.min(t[1], box_t.y1);
29926
+ let mid = staggered_turn(lo, hi, st) + shift;
29927
+ mid = Math.min(Math.max(mid, lo + 4), hi - 4);
29663
29928
  return [[s[0], mid], [t[0], mid]];
29664
29929
  }
29665
29930
  let out = 16 + Math.abs(shift);
@@ -29753,8 +30018,12 @@ function heap_pop(h) {
29753
30018
  /************************************************************
29754
30019
  * The shortest orthogonal line from `a` to `z` that enters none
29755
30020
  * of `boxes` (already expanded by the clearance), a turn costing
29756
- * ROUTE_BEND. The line leaves `a` going down and ends at `z`
29757
- * going down, since after `z` it drops into a port.
30021
+ * ROUTE_BEND. The line leaves `a` SIDEWAYS and reaches `z`
30022
+ * SIDEWAYS: the column straight under a port and the one straight
30023
+ * over it belong to the forward edges that share that port, which
30024
+ * run along them to their staggered turns -- a route going down
30025
+ * that column lay on top of them. So a routed line shares only
30026
+ * the stub between the port and `a` (or `z`).
29758
30027
  *
29759
30028
  * The grid is SPARSE: only the lines of the boxes' borders and
29760
30029
  * of the two ends, which is all an orthogonal shortest path ever
@@ -29820,7 +30089,8 @@ function orth_search(a, z, boxes) {
29820
30089
  let k = state >> 2;
29821
30090
  let dir = state & 3;
29822
30091
  if (k === goal) {
29823
- let total = cost + (dir === 2 ? 0 : 40);
30092
+ if (dir >= 2) continue;
30093
+ let total = cost + 40;
29824
30094
  if (total < found_cost) {
29825
30095
  found_cost = total;
29826
30096
  found = state;
@@ -29828,6 +30098,7 @@ function orth_search(a, z, boxes) {
29828
30098
  continue;
29829
30099
  }
29830
30100
  for (let [nk, nd, len] of moves(k)) {
30101
+ if (k === start && nd >= 2) continue;
29831
30102
  let c = cost + len + (nd === dir ? 0 : 40);
29832
30103
  let ns = nk * 4 + nd;
29833
30104
  if (c < best[ns]) {
@@ -29868,10 +30139,11 @@ function orth_search(a, z, boxes) {
29868
30139
  * coincide. Nearby cards first; all of them if that finds no way;
29869
30140
  * the simple elbow if nothing does.
29870
30141
  ************************************************************/
29871
- function elbow_route(s, t, box_s, box_t, lane, vertical, boxes) {
30142
+ function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
29872
30143
  let others = boxes || [];
29873
- if (!vertical) return elbow_route(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, others.map(swap_box)).map(swap);
29874
- let simple = elbow_points(s, t, box_s, box_t, lane, true);
30144
+ if (!vertical) return elbow_route(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, others.map(swap_box), stagger).map(swap);
30145
+ if (stagger && stagger.comb) return elbow_points(s, t, box_s, box_t, lane, true, stagger);
30146
+ let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
29875
30147
  if (!elbow_path_hits([
29876
30148
  s,
29877
30149
  ...simple,
@@ -59272,8 +59544,9 @@ var TreedbTreeLayout = class extends BaseLayout {
59272
59544
  * dragged card, a fold or a layout needs nothing else. `-v` runs
59273
59545
  * between rows (top to bottom), `-h` between columns. An edge that
59274
59546
  * does not run forward goes round the two cards, the edges joining
59275
- * the same two cards take lanes, and a line that would cross any
59276
- * other card is routed round it (treedb_elbow.js).
59547
+ * the same two cards take lanes, the edges sharing an end turn at
59548
+ * staggered heights, and a line that would cross any other card is
59549
+ * routed round it (treedb_elbow.js).
59277
59550
  ************************************************************/
59278
59551
  var ELBOW_RADIUS = 6;
59279
59552
  function elbow_box(node) {
@@ -59325,10 +59598,55 @@ function elbow_lane_of(edge) {
59325
59598
  return 0;
59326
59599
  }
59327
59600
  }
59601
+ function elbow_stagger_of(edge, vertical) {
59602
+ let graph = edge.context && edge.context.graph;
59603
+ let element = edge.context && edge.context.element;
59604
+ let s = edge.sourceNode && edge.sourceNode.id;
59605
+ let t = edge.targetNode && edge.targetNode.id;
59606
+ if (!graph || !element || s === void 0 || t === void 0) return null;
59607
+ let across = vertical ? 0 : 1;
59608
+ let along = vertical ? 1 : 0;
59609
+ let pos = (id) => graph.getElementPosition(id);
59610
+ let port_of = (node_id, key) => {
59611
+ let node = element.getElement(node_id);
59612
+ let ports = node && typeof node.getPorts === "function" ? node.getPorts() : {};
59613
+ let port = key ? ports[key] : null;
59614
+ return port ? port.getPosition() : pos(node_id);
59615
+ };
59616
+ try {
59617
+ let ps = pos(s);
59618
+ let pt = pos(t);
59619
+ let leaving = graph.getRelatedEdgesData(s, "out").filter((d) => d.source === s && pos(d.target)[along] > ps[along]);
59620
+ let combs = elbow_combs(leaving.map((d) => ({
59621
+ id: d.id,
59622
+ box: elbow_box(element.getElement(d.target))
59623
+ })), vertical);
59624
+ let dep = leaving.map((d) => {
59625
+ let comb = combs.get(d.id);
59626
+ let far = comb ? comb.lane : pos(d.target)[across];
59627
+ return {
59628
+ id: d.id,
59629
+ span: Math.abs(far - port_of(s, d.style && d.style.sourcePort)[across])
59630
+ };
59631
+ });
59632
+ let arr = graph.getRelatedEdgesData(t, "in").filter((d) => d.target === t && pos(d.source)[along] < pt[along]).map((d) => ({
59633
+ id: d.id,
59634
+ span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] - pt[across])
59635
+ }));
59636
+ return {
59637
+ dep: elbow_rank(dep, edge.id),
59638
+ arr: elbow_rank(arr, edge.id),
59639
+ comb: combs.get(edge.id) || null
59640
+ };
59641
+ } catch (e) {
59642
+ log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
59643
+ return null;
59644
+ }
59645
+ }
59328
59646
  var TreedbElbowV = class extends Polyline {
59329
59647
  getControlPoints(attributes) {
59330
59648
  let [s, t] = this.getEndpoints(attributes, false);
59331
- return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), true, elbow_obstacles(this));
59649
+ return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), true, elbow_obstacles(this), elbow_stagger_of(this, true));
59332
59650
  }
59333
59651
  getKeyPath(attributes) {
59334
59652
  return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
@@ -59337,7 +59655,7 @@ var TreedbElbowV = class extends Polyline {
59337
59655
  var TreedbElbowH = class extends Polyline {
59338
59656
  getControlPoints(attributes) {
59339
59657
  let [s, t] = this.getEndpoints(attributes, false);
59340
- return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), false, elbow_obstacles(this));
59658
+ return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), false, elbow_obstacles(this), elbow_stagger_of(this, false));
59341
59659
  }
59342
59660
  getKeyPath(attributes) {
59343
59661
  return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.152",
3
+ "version": "7.23.154",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -133,7 +133,7 @@ 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_route} from "./treedb_elbow.js";
136
+ import {elbow_lane, elbow_rank, elbow_route, elbow_combs} from "./treedb_elbow.js";
137
137
 
138
138
  import {
139
139
  BaseLayout,
@@ -4224,8 +4224,9 @@ class TreedbTreeLayout extends BaseLayout
4224
4224
  * dragged card, a fold or a layout needs nothing else. `-v` runs
4225
4225
  * between rows (top to bottom), `-h` between columns. An edge that
4226
4226
  * does not run forward goes round the two cards, the edges joining
4227
- * the same two cards take lanes, and a line that would cross any
4228
- * other card is routed round it (treedb_elbow.js).
4227
+ * the same two cards take lanes, the edges sharing an end turn at
4228
+ * staggered heights, and a line that would cross any other card is
4229
+ * routed round it (treedb_elbow.js).
4229
4230
  ************************************************************/
4230
4231
  const ELBOW_RADIUS = 6;
4231
4232
 
@@ -4297,12 +4298,73 @@ function elbow_lane_of(edge)
4297
4298
  }
4298
4299
  }
4299
4300
 
4301
+ /* Where `edge` stands among the forward edges sharing one of its
4302
+ * ends, for the stagger (treedb_elbow.js): the ones leaving its
4303
+ * source card, and the ones reaching its target card. How far each
4304
+ * runs ACROSS is measured from the real port it leaves by -- the
4305
+ * hooks of one card sit at different places along its edge. */
4306
+ function elbow_stagger_of(edge, vertical)
4307
+ {
4308
+ let graph = edge.context && edge.context.graph;
4309
+ let element = edge.context && edge.context.element;
4310
+ let s = edge.sourceNode && edge.sourceNode.id;
4311
+ let t = edge.targetNode && edge.targetNode.id;
4312
+ if(!graph || !element || s === undefined || t === undefined) {
4313
+ return null;
4314
+ }
4315
+ let across = vertical? 0 : 1;
4316
+ let along = vertical? 1 : 0;
4317
+ let pos = (id) => graph.getElementPosition(id);
4318
+ let port_of = (node_id, key) => {
4319
+ let node = element.getElement(node_id);
4320
+ let ports = (node && typeof node.getPorts === 'function')? node.getPorts() : {};
4321
+ let port = key? ports[key] : null;
4322
+ return port? port.getPosition() : pos(node_id);
4323
+ };
4324
+ try {
4325
+ let ps = pos(s);
4326
+ let pt = pos(t);
4327
+ let leaving = graph.getRelatedEdgesData(s, 'out')
4328
+ .filter((d) => d.source === s && pos(d.target)[along] > ps[along]);
4329
+ /* The cards they reach, for the combs of a stack; a comb's
4330
+ * run ends at its lane, so that is what its span measures. */
4331
+ let combs = elbow_combs(leaving.map((d) => ({
4332
+ id: d.id,
4333
+ box: elbow_box(element.getElement(d.target)),
4334
+ })), vertical);
4335
+ let dep = leaving.map((d) => {
4336
+ let comb = combs.get(d.id);
4337
+ let far = comb? comb.lane : pos(d.target)[across];
4338
+ return {
4339
+ id: d.id,
4340
+ span: Math.abs(far - port_of(s, d.style && d.style.sourcePort)[across]),
4341
+ };
4342
+ });
4343
+ let arr = graph.getRelatedEdgesData(t, 'in')
4344
+ .filter((d) => d.target === t && pos(d.source)[along] < pt[along])
4345
+ .map((d) => ({
4346
+ id: d.id,
4347
+ span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] -
4348
+ pt[across]),
4349
+ }));
4350
+ return {
4351
+ dep: elbow_rank(dep, edge.id),
4352
+ arr: elbow_rank(arr, edge.id),
4353
+ comb: combs.get(edge.id) || null,
4354
+ };
4355
+ } catch(e) {
4356
+ log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
4357
+ return null;
4358
+ }
4359
+ }
4360
+
4300
4361
  class TreedbElbowV extends Polyline
4301
4362
  {
4302
4363
  getControlPoints(attributes) {
4303
4364
  let [s, t] = this.getEndpoints(attributes, false);
4304
4365
  return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4305
- elbow_lane_of(this), true, elbow_obstacles(this));
4366
+ elbow_lane_of(this), true, elbow_obstacles(this),
4367
+ elbow_stagger_of(this, true));
4306
4368
  }
4307
4369
 
4308
4370
  getKeyPath(attributes) {
@@ -4316,7 +4378,8 @@ class TreedbElbowH extends Polyline
4316
4378
  getControlPoints(attributes) {
4317
4379
  let [s, t] = this.getEndpoints(attributes, false);
4318
4380
  return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4319
- elbow_lane_of(this), false, elbow_obstacles(this));
4381
+ elbow_lane_of(this), false, elbow_obstacles(this),
4382
+ elbow_stagger_of(this, false));
4320
4383
  }
4321
4384
 
4322
4385
  getKeyPath(attributes) {