@yuneta/gobj-ui 7.23.153 → 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.
package/README.md CHANGED
@@ -1413,7 +1413,13 @@ layouts by three thin adapters in `c_g6_nodes_tree.js`:
1413
1413
  shaped like a real one; a uniform tree is the same width. The contour
1414
1414
  arithmetic is Moen's (1990) as mxGraph wrote it in `mxCompactTreeLayout`
1415
1415
  (JGraph Ltd, Apache-2.0), read in maxGraph — a port, not a dependency,
1416
- because maxGraph's layouts drive its own graph model.
1416
+ because maxGraph's layouts drive its own graph model. Since `7.23.154` a run
1417
+ of three or more **leaf** children is **stacked**: two columns under the
1418
+ parent with a corridor between them, row after row — the contour alone
1419
+ gained nothing on a whole level opened with the stepper, where every parent
1420
+ still needed the row of its children beside it. Against `tree`: regions
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.
1417
1423
  - **`radial`** (`treedb-radial`) — the root in the middle, a ring per depth,
1418
1424
  every subtree an angular **sector** proportional to its leaves, and the
1419
1425
  radius of each ring the largest of three: one `ranksep` out from the ring
@@ -1471,7 +1477,11 @@ is short. Leaving, the farthest turns highest; arriving, the farthest turns
1471
1477
  lowest: the orders in which the lines of one end nest without crossing. A
1472
1478
  routed line leaves its port and reaches the other one **sideways**, because
1473
1479
  the column straight under or over a port is where the staggered edges of that
1474
- port run. Only the stub at the port is shared. G6's `shortest-path` router is not used: it is
1480
+ port run. Only the stub at the port is shared. Into a lower row of a stack of
1481
+ the compact tree an edge runs a **comb** (`7.23.154`): along the channel to a
1482
+ lane of its own in the stack's corridor, down it, and into the gap above its
1483
+ 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
1475
1485
  not exported, it
1476
1486
  falls back in silence to a route that crosses cards, and it rewrites its
1477
1487
  module defaults with every config it is given.
@@ -29331,6 +29331,14 @@ function tidy_tree_lr(nodes, edges, o) {
29331
29331
  * the packing across the depths differs, so switching between the
29332
29332
  * two moves cards sideways and never reorders them.
29333
29333
  *
29334
+ * And a run of three or more LEAF children -- a hall's devices, a
29335
+ * region's places when none is open -- is STACKED: two columns
29336
+ * under the parent with a corridor between them, row after row.
29337
+ * That is what makes it compact when a whole level is open, where
29338
+ * the contour alone gains nothing: every parent needed the whole
29339
+ * row of its children beside it. The tree grows taller instead of
29340
+ * wider. Its elbow edges run down the corridor (elbow_combs).
29341
+ *
29334
29342
  * The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
29335
29343
  * Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
29336
29344
  * -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
@@ -29411,10 +29419,81 @@ function contour_merge(p1, p2) {
29411
29419
  p1.lower_head = p2.lower_head;
29412
29420
  return total;
29413
29421
  }
29422
+ function stack_trunk_width(k) {
29423
+ let lanes = Math.max(0, k - 2);
29424
+ return Math.max(36, 20 + Math.max(0, lanes - 1) * 6);
29425
+ }
29426
+ function stack_geometry(t, st, col_x, col_w) {
29427
+ let d = st.depth;
29428
+ let k = st.ids.length;
29429
+ let rows = Math.ceil(k / 2);
29430
+ let side = [0, 0];
29431
+ let row_len = [];
29432
+ for (let i = 0; i < k; i++) {
29433
+ let n = t.by_id.get(st.ids[i]);
29434
+ let r = Math.floor(i / 2);
29435
+ side[i % 2] = Math.max(side[i % 2], n.h);
29436
+ row_len[r] = Math.max(row_len[r] || 0, n.w);
29437
+ }
29438
+ row_len[0] = col_w[d];
29439
+ let trunk = stack_trunk_width(k);
29440
+ let starts = [col_x[d]];
29441
+ for (let r = 1; r < rows; r++) starts[r] = starts[r - 1] + row_len[r - 1] + 40;
29442
+ return {
29443
+ side,
29444
+ trunk,
29445
+ starts,
29446
+ row_len,
29447
+ len: starts[rows - 1] + row_len[rows - 1] - col_x[d],
29448
+ ext: side[0] + trunk + side[1]
29449
+ };
29450
+ }
29451
+ function place_stack(t, st, g, top, pos) {
29452
+ for (let i = 0; i < st.ids.length; i++) {
29453
+ let n = t.by_id.get(st.ids[i]);
29454
+ let r = Math.floor(i / 2);
29455
+ let across = i % 2 === 0 ? top + g.side[0] - n.h / 2 : top + g.side[0] + g.trunk + n.h / 2;
29456
+ pos.set(st.ids[i], {
29457
+ x: g.starts[r] + g.row_len[r] / 2,
29458
+ y: across
29459
+ });
29460
+ }
29461
+ }
29414
29462
  function compact_tree_lr(nodes, edges, o) {
29415
29463
  let t = spanning_tree(nodes, edges);
29416
- let order = walk_order(t);
29417
29464
  let pos = /* @__PURE__ */ new Map();
29465
+ let kids_of = /* @__PURE__ */ new Map();
29466
+ let stacks = /* @__PURE__ */ new Map();
29467
+ for (let [id, kids] of t.children) {
29468
+ let out = [];
29469
+ let run = [];
29470
+ let flush = () => {
29471
+ if (run.length >= 3) {
29472
+ let key = { stack: stacks.size };
29473
+ stacks.set(key, {
29474
+ ids: run,
29475
+ depth: t.depth.get(run[0])
29476
+ });
29477
+ out.push(key);
29478
+ } else out.push(...run);
29479
+ run = [];
29480
+ };
29481
+ for (let kid of kids) if ((t.children.get(kid) || []).length === 0) run.push(kid);
29482
+ else {
29483
+ flush();
29484
+ out.push(kid);
29485
+ }
29486
+ flush();
29487
+ kids_of.set(id, out);
29488
+ }
29489
+ let order = [];
29490
+ let todo = t.roots.slice().reverse();
29491
+ while (todo.length) {
29492
+ let id = todo.pop();
29493
+ order.push(id);
29494
+ let kids = kids_of.get(id) || [];
29495
+ for (let i = kids.length - 1; i >= 0; i--) todo.push(kids[i]);
29496
+ }
29418
29497
  let col_w = [];
29419
29498
  for (let [id, d] of t.depth) col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
29420
29499
  let col_x = [];
@@ -29427,18 +29506,32 @@ function compact_tree_lr(nodes, edges, o) {
29427
29506
  let dist = o.nodesep;
29428
29507
  let level_distance = o.ranksep - pad;
29429
29508
  let tn = /* @__PURE__ */ new Map();
29430
- for (let id of order) tn.set(id, {
29431
- len: col_w[t.depth.get(id)],
29432
- ext: t.by_id.get(id).h,
29433
- off_x: 0,
29434
- off_y: 0,
29435
- y: 0,
29436
- contour: null
29437
- });
29509
+ for (let id of order) {
29510
+ let st = stacks.get(id);
29511
+ if (st) {
29512
+ let g = stack_geometry(t, st, col_x, col_w);
29513
+ tn.set(id, {
29514
+ len: g.len,
29515
+ ext: g.ext,
29516
+ geom: g,
29517
+ off_x: 0,
29518
+ off_y: 0,
29519
+ y: 0,
29520
+ contour: null
29521
+ });
29522
+ } else tn.set(id, {
29523
+ len: col_w[t.depth.get(id)],
29524
+ ext: t.by_id.get(id).h,
29525
+ off_x: 0,
29526
+ off_y: 0,
29527
+ y: 0,
29528
+ contour: null
29529
+ });
29530
+ }
29438
29531
  for (let i = order.length - 1; i >= 0; i--) {
29439
29532
  let id = order[i];
29440
29533
  let n = tn.get(id);
29441
- let kids = t.children.get(id) || [];
29534
+ let kids = kids_of.get(id) || [];
29442
29535
  if (!kids.length) {
29443
29536
  let upper = contour_line(n.len + dist, 0);
29444
29537
  let lower_tail = contour_line(0, -n.ext - dist);
@@ -29474,7 +29567,7 @@ function compact_tree_lr(nodes, edges, o) {
29474
29567
  for (let id of order) {
29475
29568
  let n = tn.get(id);
29476
29569
  if (!root_of.has(id)) root_of.set(id, id);
29477
- let kids = t.children.get(id) || [];
29570
+ let kids = kids_of.get(id) || [];
29478
29571
  if (!kids.length) continue;
29479
29572
  let running = n.y + tn.get(kids[0]).off_y;
29480
29573
  tn.get(kids[0]).y = running;
@@ -29501,9 +29594,14 @@ function compact_tree_lr(nodes, edges, o) {
29501
29594
  top += hi.get(r) - lo.get(r) + o.nodesep * 2;
29502
29595
  }
29503
29596
  for (let id of order) {
29504
- let d = t.depth.get(id);
29505
29597
  let n = tn.get(id);
29506
29598
  let y = n.y + shift.get(root_of.get(id));
29599
+ let st = stacks.get(id);
29600
+ if (st) {
29601
+ place_stack(t, st, n.geom, y, pos);
29602
+ continue;
29603
+ }
29604
+ let d = t.depth.get(id);
29507
29605
  pos.set(id, {
29508
29606
  x: col_x[d] + col_w[d] / 2,
29509
29607
  y: y + n.ext / 2
@@ -29668,6 +29766,136 @@ function swap_box(b) {
29668
29766
  y2: b.x2
29669
29767
  };
29670
29768
  }
29769
+ function staggered_turn(lo, hi, st) {
29770
+ let mid = (lo + hi) / 2;
29771
+ let group = null;
29772
+ let k = 0;
29773
+ if (st.dep && st.dep.count > 1) {
29774
+ group = st.dep;
29775
+ k = st.dep.rank;
29776
+ } else if (st.arr && st.arr.count > 1) {
29777
+ group = st.arr;
29778
+ k = st.arr.count - 1 - st.arr.rank;
29779
+ }
29780
+ if (group) {
29781
+ let room = Math.max(0, hi - lo - 12);
29782
+ let step = Math.min(10, room / (group.count - 1));
29783
+ mid = mid - (group.count - 1) * step / 2 + k * step;
29784
+ }
29785
+ return mid;
29786
+ }
29787
+ /************************************************************
29788
+ * The combs of the stacks under one card.
29789
+ *
29790
+ * treedb_layout.js lays a run of leaf children out as two columns
29791
+ * with a corridor between them. A card in the first row is reached
29792
+ * from the channel like any other; one in a lower row cannot be --
29793
+ * the card above it stands in the way -- so its edge runs down
29794
+ * the corridor in a LANE of its own and turns into the gap above
29795
+ * its card, which it shares only with the other card of its row,
29796
+ * reached from the other side.
29797
+ *
29798
+ * targets [{id, box}]: the forward edges leaving one card,
29799
+ * each with the box of the card it reaches
29800
+ * vertical as in elbow_points; false swaps the boxes
29801
+ *
29802
+ * Returns Map id -> {lane, z, top}: the lane, the height of the
29803
+ * gap it turns into, and the top of the stack's first row (where
29804
+ * the channel over the stack ends). Only for a card that stands
29805
+ * under another of the group, within a stack gap and lined up
29806
+ * with it on the corridor side -- what a stack looks like.
29807
+ *
29808
+ * Lanes go outermost first, row by row: a line into a higher row
29809
+ * keeps outside the lines going deeper, and none crosses another.
29810
+ ************************************************************/
29811
+ function elbow_combs(targets, vertical) {
29812
+ let list = vertical ? targets : targets.map((it) => ({
29813
+ id: it.id,
29814
+ box: swap_box(it.box)
29815
+ }));
29816
+ let cy = (b) => (b.y1 + b.y2) / 2;
29817
+ let near = (a, b) => Math.abs(a - b) < 1.5;
29818
+ let under = [];
29819
+ for (let it of list) {
29820
+ let b = it.box;
29821
+ let above = null;
29822
+ for (let o of list) {
29823
+ let a = o.box;
29824
+ if (o === it || a.y2 > b.y1 || b.y1 - a.y2 > 40) continue;
29825
+ if (!(near(a.x2, b.x2) || near(a.x1, b.x1))) continue;
29826
+ if (!above || a.y2 > above.y2) above = a;
29827
+ }
29828
+ if (!above) continue;
29829
+ let side;
29830
+ if (near(above.x2, b.x2) && !near(above.x1, b.x1)) side = 0;
29831
+ else if (near(above.x1, b.x1) && !near(above.x2, b.x2)) side = 1;
29832
+ else side = list.some((o) => o !== it && near(cy(o.box), cy(b)) && o.box.x2 <= b.x1) ? 1 : 0;
29833
+ under.push({
29834
+ id: it.id,
29835
+ box: b,
29836
+ above,
29837
+ side
29838
+ });
29839
+ }
29840
+ let corridor = (u) => {
29841
+ if (u.side === 0) {
29842
+ let l = u.box.x2;
29843
+ let col = list.filter((o) => near(o.box.x2, l));
29844
+ let r = Infinity;
29845
+ 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);
29846
+ return {
29847
+ l,
29848
+ r: isFinite(r) ? r : l + 36,
29849
+ col
29850
+ };
29851
+ }
29852
+ let r = u.box.x1;
29853
+ let col = list.filter((o) => near(o.box.x1, r));
29854
+ let l = -Infinity;
29855
+ 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);
29856
+ return {
29857
+ l: isFinite(l) ? l : r - 36,
29858
+ r,
29859
+ col
29860
+ };
29861
+ };
29862
+ let groups = /* @__PURE__ */ new Map();
29863
+ for (let u of under) {
29864
+ let c = corridor(u);
29865
+ u.top = Math.min(...c.col.map((o) => o.box.y1));
29866
+ let key = `${Math.round(c.l)}|${Math.round(c.r)}`;
29867
+ if (!groups.has(key)) groups.set(key, {
29868
+ l: c.l,
29869
+ r: c.r,
29870
+ left: [],
29871
+ right: []
29872
+ });
29873
+ groups.get(key)[u.side === 0 ? "left" : "right"].push(u);
29874
+ }
29875
+ let out = /* @__PURE__ */ new Map();
29876
+ for (let g of groups.values()) {
29877
+ g.left.sort((a, b) => a.box.y1 - b.box.y1);
29878
+ g.right.sort((a, b) => a.box.y1 - b.box.y1);
29879
+ let n = g.left.length + g.right.length;
29880
+ let room = g.r - g.l - 20;
29881
+ let step = n > 1 ? Math.max(0, Math.min(6, room / (n - 1))) : 0;
29882
+ g.left.forEach((u, i) => {
29883
+ out.set(u.id, {
29884
+ lane: g.l + 10 + i * step,
29885
+ z: (u.above.y2 + u.box.y1) / 2,
29886
+ top: u.top
29887
+ });
29888
+ });
29889
+ g.right.forEach((u, j) => {
29890
+ out.set(u.id, {
29891
+ lane: g.r - 10 - j * step,
29892
+ z: (u.above.y2 + u.box.y1) / 2,
29893
+ top: u.top
29894
+ });
29895
+ });
29896
+ }
29897
+ return out;
29898
+ }
29671
29899
  /************************************************************
29672
29900
  * The control points of an elbow from the port `s` to the port
29673
29901
  * `t` ([x, y] each), knowing the boxes of the two cards
@@ -29675,34 +29903,32 @@ function swap_box(b) {
29675
29903
  *
29676
29904
  * vertical the rows are stacked (top to bottom); false
29677
29905
  * when they are side by side (left to right)
29678
- * stagger {dep, arr}: the edge's rank among the forward
29679
- * edges leaving its source card and among those
29680
- * reaching its target card (elbow_rank). Absent,
29681
- * the channel is the middle.
29906
+ * stagger {dep, arr, comb}: the edge's rank among the
29907
+ * forward edges leaving its source card and among
29908
+ * those reaching its target card (elbow_rank), and
29909
+ * its comb when the target is in a lower row of a
29910
+ * stack (elbow_combs). Absent, the channel is the
29911
+ * middle.
29682
29912
  ************************************************************/
29683
29913
  function elbow_points(s, t, box_s, box_t, lane, vertical, stagger) {
29684
29914
  if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, stagger).map(swap);
29685
29915
  let shift = lane * 10;
29686
29916
  if (t[1] - s[1] > 20) {
29917
+ let st = stagger || {};
29687
29918
  let lo = Math.max(s[1], box_s.y2);
29919
+ if (st.comb) {
29920
+ let c = st.comb;
29921
+ let turn = staggered_turn(lo, c.top, { dep: st.dep }) + shift;
29922
+ turn = Math.min(Math.max(turn, lo + 4), c.top - 4);
29923
+ return [
29924
+ [s[0], turn],
29925
+ [c.lane, turn],
29926
+ [c.lane, c.z],
29927
+ [t[0], c.z]
29928
+ ];
29929
+ }
29688
29930
  let hi = Math.min(t[1], box_t.y1);
29689
- let mid = (lo + hi) / 2;
29690
- let st = stagger || {};
29691
- let group = null;
29692
- let k = 0;
29693
- if (st.dep && st.dep.count > 1) {
29694
- group = st.dep;
29695
- k = st.dep.rank;
29696
- } else if (st.arr && st.arr.count > 1) {
29697
- group = st.arr;
29698
- k = st.arr.count - 1 - st.arr.rank;
29699
- }
29700
- if (group) {
29701
- let room = Math.max(0, hi - lo - 12);
29702
- let step = Math.min(10, room / (group.count - 1));
29703
- mid = mid - (group.count - 1) * step / 2 + k * step;
29704
- }
29705
- mid += shift;
29931
+ let mid = staggered_turn(lo, hi, st) + shift;
29706
29932
  mid = Math.min(Math.max(mid, lo + 4), hi - 4);
29707
29933
  return [[s[0], mid], [t[0], mid]];
29708
29934
  }
@@ -29921,6 +30147,7 @@ function orth_search(a, z, boxes) {
29921
30147
  function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
29922
30148
  let others = boxes || [];
29923
30149
  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);
30150
+ if (stagger && stagger.comb) return elbow_points(s, t, box_s, box_t, lane, true, stagger);
29924
30151
  let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
29925
30152
  if (!elbow_path_hits([
29926
30153
  s,
@@ -59394,17 +59621,27 @@ function elbow_stagger_of(edge, vertical) {
59394
59621
  try {
59395
59622
  let ps = pos(s);
59396
59623
  let pt = pos(t);
59397
- let dep = graph.getRelatedEdgesData(s, "out").filter((d) => d.source === s && pos(d.target)[along] > ps[along]).map((d) => ({
59624
+ let leaving = graph.getRelatedEdgesData(s, "out").filter((d) => d.source === s && pos(d.target)[along] > ps[along]);
59625
+ let combs = elbow_combs(leaving.map((d) => ({
59398
59626
  id: d.id,
59399
- span: Math.abs(pos(d.target)[across] - port_of(s, d.style && d.style.sourcePort)[across])
59400
- }));
59627
+ box: elbow_box(element.getElement(d.target))
59628
+ })), vertical);
59629
+ let dep = leaving.map((d) => {
59630
+ let comb = combs.get(d.id);
59631
+ let far = comb ? comb.lane : pos(d.target)[across];
59632
+ return {
59633
+ id: d.id,
59634
+ span: Math.abs(far - port_of(s, d.style && d.style.sourcePort)[across])
59635
+ };
59636
+ });
59401
59637
  let arr = graph.getRelatedEdgesData(t, "in").filter((d) => d.target === t && pos(d.source)[along] < pt[along]).map((d) => ({
59402
59638
  id: d.id,
59403
59639
  span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] - pt[across])
59404
59640
  }));
59405
59641
  return {
59406
59642
  dep: elbow_rank(dep, edge.id),
59407
- arr: elbow_rank(arr, edge.id)
59643
+ arr: elbow_rank(arr, edge.id),
59644
+ comb: combs.get(edge.id) || null
59408
59645
  };
59409
59646
  } catch (e) {
59410
59647
  (0, _yuneta_gobj_js.log_error)(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);