@yuneta/gobj-ui 7.23.152 → 7.23.153
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 +13 -2
- package/dist/gobj-ui.cjs.js +95 -14
- package/dist/gobj-ui.es.js +95 -14
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +55 -5
- package/src/treedb_elbow.js +74 -13
- package/src/treedb_elbow.test.js +107 -1
package/README.md
CHANGED
|
@@ -1460,8 +1460,19 @@ what gets routed is a detour with a third card in its way, an edge dropping
|
|
|
1460
1460
|
several rows, an edge between two cards of one row. Since `7.23.152` a node is
|
|
1461
1461
|
measured **whole** — card, ports and label — so a line keeps clear of the
|
|
1462
1462
|
ports as well; the edge's own ports stick out of its own cards, so its first
|
|
1463
|
-
and last segments are not tested against their own box.
|
|
1464
|
-
|
|
1463
|
+
and last segments are not tested against their own box.
|
|
1464
|
+
|
|
1465
|
+
The edges sharing an **end** do not lie on top of each other either
|
|
1466
|
+
(`7.23.153`): the forward edges leaving one card — all of them, not only one
|
|
1467
|
+
port's, or two hooks side by side would stack their runs — and those reaching
|
|
1468
|
+
one card (all its fkeys enter by one point) are **staggered**, each turning at
|
|
1469
|
+
its own height in the channel, 10px apart at most and closer when the channel
|
|
1470
|
+
is short. Leaving, the farthest turns highest; arriving, the farthest turns
|
|
1471
|
+
lowest: the orders in which the lines of one end nest without crossing. A
|
|
1472
|
+
routed line leaves its port and reaches the other one **sideways**, because
|
|
1473
|
+
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
|
|
1475
|
+
not exported, it
|
|
1465
1476
|
falls back in silence to a route that crosses cards, and it rewrites its
|
|
1466
1477
|
module defaults with every config it is given.
|
|
1467
1478
|
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -29640,6 +29640,23 @@ function elbow_lane(ids, id) {
|
|
|
29640
29640
|
if (k <= 0) return 0;
|
|
29641
29641
|
return (k % 2 ? 1 : -1) * Math.ceil(k / 2);
|
|
29642
29642
|
}
|
|
29643
|
+
/************************************************************
|
|
29644
|
+
* The rank of `id` among `items` ([{id, span}]): the forward
|
|
29645
|
+
* edges that share one end with it, each with how far it runs
|
|
29646
|
+
* ACROSS. Farthest first; a tie goes to the edge made first.
|
|
29647
|
+
* Returns {rank, count}.
|
|
29648
|
+
************************************************************/
|
|
29649
|
+
function elbow_rank(items, id) {
|
|
29650
|
+
let sorted = items.slice().sort((a, b) => {
|
|
29651
|
+
if (b.span !== a.span) return b.span - a.span;
|
|
29652
|
+
return String(a.id).localeCompare(String(b.id), void 0, { numeric: true });
|
|
29653
|
+
});
|
|
29654
|
+
let rank = sorted.findIndex((it) => it.id === id);
|
|
29655
|
+
return {
|
|
29656
|
+
rank: rank < 0 ? 0 : rank,
|
|
29657
|
+
count: sorted.length
|
|
29658
|
+
};
|
|
29659
|
+
}
|
|
29643
29660
|
function swap(p) {
|
|
29644
29661
|
return [p[1], p[0]];
|
|
29645
29662
|
}
|
|
@@ -29658,13 +29675,35 @@ function swap_box(b) {
|
|
|
29658
29675
|
*
|
|
29659
29676
|
* vertical the rows are stacked (top to bottom); false
|
|
29660
29677
|
* 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.
|
|
29661
29682
|
************************************************************/
|
|
29662
|
-
function elbow_points(s, t, box_s, box_t, lane, vertical) {
|
|
29663
|
-
if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true).map(swap);
|
|
29683
|
+
function elbow_points(s, t, box_s, box_t, lane, vertical, stagger) {
|
|
29684
|
+
if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, stagger).map(swap);
|
|
29664
29685
|
let shift = lane * 10;
|
|
29665
29686
|
if (t[1] - s[1] > 20) {
|
|
29666
|
-
let
|
|
29667
|
-
|
|
29687
|
+
let lo = Math.max(s[1], box_s.y2);
|
|
29688
|
+
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;
|
|
29706
|
+
mid = Math.min(Math.max(mid, lo + 4), hi - 4);
|
|
29668
29707
|
return [[s[0], mid], [t[0], mid]];
|
|
29669
29708
|
}
|
|
29670
29709
|
let out = 16 + Math.abs(shift);
|
|
@@ -29758,8 +29797,12 @@ function heap_pop(h) {
|
|
|
29758
29797
|
/************************************************************
|
|
29759
29798
|
* The shortest orthogonal line from `a` to `z` that enters none
|
|
29760
29799
|
* of `boxes` (already expanded by the clearance), a turn costing
|
|
29761
|
-
* ROUTE_BEND. The line leaves `a`
|
|
29762
|
-
*
|
|
29800
|
+
* ROUTE_BEND. The line leaves `a` SIDEWAYS and reaches `z`
|
|
29801
|
+
* SIDEWAYS: the column straight under a port and the one straight
|
|
29802
|
+
* over it belong to the forward edges that share that port, which
|
|
29803
|
+
* run along them to their staggered turns -- a route going down
|
|
29804
|
+
* that column lay on top of them. So a routed line shares only
|
|
29805
|
+
* the stub between the port and `a` (or `z`).
|
|
29763
29806
|
*
|
|
29764
29807
|
* The grid is SPARSE: only the lines of the boxes' borders and
|
|
29765
29808
|
* of the two ends, which is all an orthogonal shortest path ever
|
|
@@ -29825,7 +29868,8 @@ function orth_search(a, z, boxes) {
|
|
|
29825
29868
|
let k = state >> 2;
|
|
29826
29869
|
let dir = state & 3;
|
|
29827
29870
|
if (k === goal) {
|
|
29828
|
-
|
|
29871
|
+
if (dir >= 2) continue;
|
|
29872
|
+
let total = cost + 40;
|
|
29829
29873
|
if (total < found_cost) {
|
|
29830
29874
|
found_cost = total;
|
|
29831
29875
|
found = state;
|
|
@@ -29833,6 +29877,7 @@ function orth_search(a, z, boxes) {
|
|
|
29833
29877
|
continue;
|
|
29834
29878
|
}
|
|
29835
29879
|
for (let [nk, nd, len] of moves(k)) {
|
|
29880
|
+
if (k === start && nd >= 2) continue;
|
|
29836
29881
|
let c = cost + len + (nd === dir ? 0 : 40);
|
|
29837
29882
|
let ns = nk * 4 + nd;
|
|
29838
29883
|
if (c < best[ns]) {
|
|
@@ -29873,10 +29918,10 @@ function orth_search(a, z, boxes) {
|
|
|
29873
29918
|
* coincide. Nearby cards first; all of them if that finds no way;
|
|
29874
29919
|
* the simple elbow if nothing does.
|
|
29875
29920
|
************************************************************/
|
|
29876
|
-
function elbow_route(s, t, box_s, box_t, lane, vertical, boxes) {
|
|
29921
|
+
function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
|
|
29877
29922
|
let others = boxes || [];
|
|
29878
|
-
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);
|
|
29879
|
-
let simple = elbow_points(s, t, box_s, box_t, lane, true);
|
|
29923
|
+
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);
|
|
29924
|
+
let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
|
|
29880
29925
|
if (!elbow_path_hits([
|
|
29881
29926
|
s,
|
|
29882
29927
|
...simple,
|
|
@@ -59277,8 +59322,9 @@ var TreedbTreeLayout = class extends _antv_g6.BaseLayout {
|
|
|
59277
59322
|
* dragged card, a fold or a layout needs nothing else. `-v` runs
|
|
59278
59323
|
* between rows (top to bottom), `-h` between columns. An edge that
|
|
59279
59324
|
* does not run forward goes round the two cards, the edges joining
|
|
59280
|
-
* the same two cards take lanes,
|
|
59281
|
-
*
|
|
59325
|
+
* the same two cards take lanes, the edges sharing an end turn at
|
|
59326
|
+
* staggered heights, and a line that would cross any other card is
|
|
59327
|
+
* routed round it (treedb_elbow.js).
|
|
59282
59328
|
************************************************************/
|
|
59283
59329
|
var ELBOW_RADIUS = 6;
|
|
59284
59330
|
function elbow_box(node) {
|
|
@@ -59330,10 +59376,45 @@ function elbow_lane_of(edge) {
|
|
|
59330
59376
|
return 0;
|
|
59331
59377
|
}
|
|
59332
59378
|
}
|
|
59379
|
+
function elbow_stagger_of(edge, vertical) {
|
|
59380
|
+
let graph = edge.context && edge.context.graph;
|
|
59381
|
+
let element = edge.context && edge.context.element;
|
|
59382
|
+
let s = edge.sourceNode && edge.sourceNode.id;
|
|
59383
|
+
let t = edge.targetNode && edge.targetNode.id;
|
|
59384
|
+
if (!graph || !element || s === void 0 || t === void 0) return null;
|
|
59385
|
+
let across = vertical ? 0 : 1;
|
|
59386
|
+
let along = vertical ? 1 : 0;
|
|
59387
|
+
let pos = (id) => graph.getElementPosition(id);
|
|
59388
|
+
let port_of = (node_id, key) => {
|
|
59389
|
+
let node = element.getElement(node_id);
|
|
59390
|
+
let ports = node && typeof node.getPorts === "function" ? node.getPorts() : {};
|
|
59391
|
+
let port = key ? ports[key] : null;
|
|
59392
|
+
return port ? port.getPosition() : pos(node_id);
|
|
59393
|
+
};
|
|
59394
|
+
try {
|
|
59395
|
+
let ps = pos(s);
|
|
59396
|
+
let pt = pos(t);
|
|
59397
|
+
let dep = graph.getRelatedEdgesData(s, "out").filter((d) => d.source === s && pos(d.target)[along] > ps[along]).map((d) => ({
|
|
59398
|
+
id: d.id,
|
|
59399
|
+
span: Math.abs(pos(d.target)[across] - port_of(s, d.style && d.style.sourcePort)[across])
|
|
59400
|
+
}));
|
|
59401
|
+
let arr = graph.getRelatedEdgesData(t, "in").filter((d) => d.target === t && pos(d.source)[along] < pt[along]).map((d) => ({
|
|
59402
|
+
id: d.id,
|
|
59403
|
+
span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] - pt[across])
|
|
59404
|
+
}));
|
|
59405
|
+
return {
|
|
59406
|
+
dep: elbow_rank(dep, edge.id),
|
|
59407
|
+
arr: elbow_rank(arr, edge.id)
|
|
59408
|
+
};
|
|
59409
|
+
} catch (e) {
|
|
59410
|
+
(0, _yuneta_gobj_js.log_error)(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
|
|
59411
|
+
return null;
|
|
59412
|
+
}
|
|
59413
|
+
}
|
|
59333
59414
|
var TreedbElbowV = class extends _antv_g6.Polyline {
|
|
59334
59415
|
getControlPoints(attributes) {
|
|
59335
59416
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
59336
|
-
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), true, elbow_obstacles(this));
|
|
59417
|
+
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));
|
|
59337
59418
|
}
|
|
59338
59419
|
getKeyPath(attributes) {
|
|
59339
59420
|
return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
|
|
@@ -59342,7 +59423,7 @@ var TreedbElbowV = class extends _antv_g6.Polyline {
|
|
|
59342
59423
|
var TreedbElbowH = class extends _antv_g6.Polyline {
|
|
59343
59424
|
getControlPoints(attributes) {
|
|
59344
59425
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
59345
|
-
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), false, elbow_obstacles(this));
|
|
59426
|
+
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));
|
|
59346
59427
|
}
|
|
59347
59428
|
getKeyPath(attributes) {
|
|
59348
59429
|
return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -29635,6 +29635,23 @@ function elbow_lane(ids, id) {
|
|
|
29635
29635
|
if (k <= 0) return 0;
|
|
29636
29636
|
return (k % 2 ? 1 : -1) * Math.ceil(k / 2);
|
|
29637
29637
|
}
|
|
29638
|
+
/************************************************************
|
|
29639
|
+
* The rank of `id` among `items` ([{id, span}]): the forward
|
|
29640
|
+
* edges that share one end with it, each with how far it runs
|
|
29641
|
+
* ACROSS. Farthest first; a tie goes to the edge made first.
|
|
29642
|
+
* Returns {rank, count}.
|
|
29643
|
+
************************************************************/
|
|
29644
|
+
function elbow_rank(items, id) {
|
|
29645
|
+
let sorted = items.slice().sort((a, b) => {
|
|
29646
|
+
if (b.span !== a.span) return b.span - a.span;
|
|
29647
|
+
return String(a.id).localeCompare(String(b.id), void 0, { numeric: true });
|
|
29648
|
+
});
|
|
29649
|
+
let rank = sorted.findIndex((it) => it.id === id);
|
|
29650
|
+
return {
|
|
29651
|
+
rank: rank < 0 ? 0 : rank,
|
|
29652
|
+
count: sorted.length
|
|
29653
|
+
};
|
|
29654
|
+
}
|
|
29638
29655
|
function swap(p) {
|
|
29639
29656
|
return [p[1], p[0]];
|
|
29640
29657
|
}
|
|
@@ -29653,13 +29670,35 @@ function swap_box(b) {
|
|
|
29653
29670
|
*
|
|
29654
29671
|
* vertical the rows are stacked (top to bottom); false
|
|
29655
29672
|
* when they are side by side (left to right)
|
|
29673
|
+
* stagger {dep, arr}: the edge's rank among the forward
|
|
29674
|
+
* edges leaving its source card and among those
|
|
29675
|
+
* reaching its target card (elbow_rank). Absent,
|
|
29676
|
+
* the channel is the middle.
|
|
29656
29677
|
************************************************************/
|
|
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);
|
|
29678
|
+
function elbow_points(s, t, box_s, box_t, lane, vertical, stagger) {
|
|
29679
|
+
if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true, stagger).map(swap);
|
|
29659
29680
|
let shift = lane * 10;
|
|
29660
29681
|
if (t[1] - s[1] > 20) {
|
|
29661
|
-
let
|
|
29662
|
-
|
|
29682
|
+
let lo = Math.max(s[1], box_s.y2);
|
|
29683
|
+
let hi = Math.min(t[1], box_t.y1);
|
|
29684
|
+
let mid = (lo + hi) / 2;
|
|
29685
|
+
let st = stagger || {};
|
|
29686
|
+
let group = null;
|
|
29687
|
+
let k = 0;
|
|
29688
|
+
if (st.dep && st.dep.count > 1) {
|
|
29689
|
+
group = st.dep;
|
|
29690
|
+
k = st.dep.rank;
|
|
29691
|
+
} else if (st.arr && st.arr.count > 1) {
|
|
29692
|
+
group = st.arr;
|
|
29693
|
+
k = st.arr.count - 1 - st.arr.rank;
|
|
29694
|
+
}
|
|
29695
|
+
if (group) {
|
|
29696
|
+
let room = Math.max(0, hi - lo - 12);
|
|
29697
|
+
let step = Math.min(10, room / (group.count - 1));
|
|
29698
|
+
mid = mid - (group.count - 1) * step / 2 + k * step;
|
|
29699
|
+
}
|
|
29700
|
+
mid += shift;
|
|
29701
|
+
mid = Math.min(Math.max(mid, lo + 4), hi - 4);
|
|
29663
29702
|
return [[s[0], mid], [t[0], mid]];
|
|
29664
29703
|
}
|
|
29665
29704
|
let out = 16 + Math.abs(shift);
|
|
@@ -29753,8 +29792,12 @@ function heap_pop(h) {
|
|
|
29753
29792
|
/************************************************************
|
|
29754
29793
|
* The shortest orthogonal line from `a` to `z` that enters none
|
|
29755
29794
|
* of `boxes` (already expanded by the clearance), a turn costing
|
|
29756
|
-
* ROUTE_BEND. The line leaves `a`
|
|
29757
|
-
*
|
|
29795
|
+
* ROUTE_BEND. The line leaves `a` SIDEWAYS and reaches `z`
|
|
29796
|
+
* SIDEWAYS: the column straight under a port and the one straight
|
|
29797
|
+
* over it belong to the forward edges that share that port, which
|
|
29798
|
+
* run along them to their staggered turns -- a route going down
|
|
29799
|
+
* that column lay on top of them. So a routed line shares only
|
|
29800
|
+
* the stub between the port and `a` (or `z`).
|
|
29758
29801
|
*
|
|
29759
29802
|
* The grid is SPARSE: only the lines of the boxes' borders and
|
|
29760
29803
|
* of the two ends, which is all an orthogonal shortest path ever
|
|
@@ -29820,7 +29863,8 @@ function orth_search(a, z, boxes) {
|
|
|
29820
29863
|
let k = state >> 2;
|
|
29821
29864
|
let dir = state & 3;
|
|
29822
29865
|
if (k === goal) {
|
|
29823
|
-
|
|
29866
|
+
if (dir >= 2) continue;
|
|
29867
|
+
let total = cost + 40;
|
|
29824
29868
|
if (total < found_cost) {
|
|
29825
29869
|
found_cost = total;
|
|
29826
29870
|
found = state;
|
|
@@ -29828,6 +29872,7 @@ function orth_search(a, z, boxes) {
|
|
|
29828
29872
|
continue;
|
|
29829
29873
|
}
|
|
29830
29874
|
for (let [nk, nd, len] of moves(k)) {
|
|
29875
|
+
if (k === start && nd >= 2) continue;
|
|
29831
29876
|
let c = cost + len + (nd === dir ? 0 : 40);
|
|
29832
29877
|
let ns = nk * 4 + nd;
|
|
29833
29878
|
if (c < best[ns]) {
|
|
@@ -29868,10 +29913,10 @@ function orth_search(a, z, boxes) {
|
|
|
29868
29913
|
* coincide. Nearby cards first; all of them if that finds no way;
|
|
29869
29914
|
* the simple elbow if nothing does.
|
|
29870
29915
|
************************************************************/
|
|
29871
|
-
function elbow_route(s, t, box_s, box_t, lane, vertical, boxes) {
|
|
29916
|
+
function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger) {
|
|
29872
29917
|
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);
|
|
29918
|
+
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);
|
|
29919
|
+
let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
|
|
29875
29920
|
if (!elbow_path_hits([
|
|
29876
29921
|
s,
|
|
29877
29922
|
...simple,
|
|
@@ -59272,8 +59317,9 @@ var TreedbTreeLayout = class extends BaseLayout {
|
|
|
59272
59317
|
* dragged card, a fold or a layout needs nothing else. `-v` runs
|
|
59273
59318
|
* between rows (top to bottom), `-h` between columns. An edge that
|
|
59274
59319
|
* does not run forward goes round the two cards, the edges joining
|
|
59275
|
-
* the same two cards take lanes,
|
|
59276
|
-
*
|
|
59320
|
+
* the same two cards take lanes, the edges sharing an end turn at
|
|
59321
|
+
* staggered heights, and a line that would cross any other card is
|
|
59322
|
+
* routed round it (treedb_elbow.js).
|
|
59277
59323
|
************************************************************/
|
|
59278
59324
|
var ELBOW_RADIUS = 6;
|
|
59279
59325
|
function elbow_box(node) {
|
|
@@ -59325,10 +59371,45 @@ function elbow_lane_of(edge) {
|
|
|
59325
59371
|
return 0;
|
|
59326
59372
|
}
|
|
59327
59373
|
}
|
|
59374
|
+
function elbow_stagger_of(edge, vertical) {
|
|
59375
|
+
let graph = edge.context && edge.context.graph;
|
|
59376
|
+
let element = edge.context && edge.context.element;
|
|
59377
|
+
let s = edge.sourceNode && edge.sourceNode.id;
|
|
59378
|
+
let t = edge.targetNode && edge.targetNode.id;
|
|
59379
|
+
if (!graph || !element || s === void 0 || t === void 0) return null;
|
|
59380
|
+
let across = vertical ? 0 : 1;
|
|
59381
|
+
let along = vertical ? 1 : 0;
|
|
59382
|
+
let pos = (id) => graph.getElementPosition(id);
|
|
59383
|
+
let port_of = (node_id, key) => {
|
|
59384
|
+
let node = element.getElement(node_id);
|
|
59385
|
+
let ports = node && typeof node.getPorts === "function" ? node.getPorts() : {};
|
|
59386
|
+
let port = key ? ports[key] : null;
|
|
59387
|
+
return port ? port.getPosition() : pos(node_id);
|
|
59388
|
+
};
|
|
59389
|
+
try {
|
|
59390
|
+
let ps = pos(s);
|
|
59391
|
+
let pt = pos(t);
|
|
59392
|
+
let dep = graph.getRelatedEdgesData(s, "out").filter((d) => d.source === s && pos(d.target)[along] > ps[along]).map((d) => ({
|
|
59393
|
+
id: d.id,
|
|
59394
|
+
span: Math.abs(pos(d.target)[across] - port_of(s, d.style && d.style.sourcePort)[across])
|
|
59395
|
+
}));
|
|
59396
|
+
let arr = graph.getRelatedEdgesData(t, "in").filter((d) => d.target === t && pos(d.source)[along] < pt[along]).map((d) => ({
|
|
59397
|
+
id: d.id,
|
|
59398
|
+
span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] - pt[across])
|
|
59399
|
+
}));
|
|
59400
|
+
return {
|
|
59401
|
+
dep: elbow_rank(dep, edge.id),
|
|
59402
|
+
arr: elbow_rank(arr, edge.id)
|
|
59403
|
+
};
|
|
59404
|
+
} catch (e) {
|
|
59405
|
+
log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
|
|
59406
|
+
return null;
|
|
59407
|
+
}
|
|
59408
|
+
}
|
|
59328
59409
|
var TreedbElbowV = class extends Polyline {
|
|
59329
59410
|
getControlPoints(attributes) {
|
|
59330
59411
|
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));
|
|
59412
|
+
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
59413
|
}
|
|
59333
59414
|
getKeyPath(attributes) {
|
|
59334
59415
|
return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
|
|
@@ -59337,7 +59418,7 @@ var TreedbElbowV = class extends Polyline {
|
|
|
59337
59418
|
var TreedbElbowH = class extends Polyline {
|
|
59338
59419
|
getControlPoints(attributes) {
|
|
59339
59420
|
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));
|
|
59421
|
+
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
59422
|
}
|
|
59342
59423
|
getKeyPath(attributes) {
|
|
59343
59424
|
return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -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} 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,
|
|
4228
|
-
*
|
|
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,60 @@ 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 dep = graph.getRelatedEdgesData(s, 'out')
|
|
4328
|
+
.filter((d) => d.source === s && pos(d.target)[along] > ps[along])
|
|
4329
|
+
.map((d) => ({
|
|
4330
|
+
id: d.id,
|
|
4331
|
+
span: Math.abs(pos(d.target)[across] -
|
|
4332
|
+
port_of(s, d.style && d.style.sourcePort)[across]),
|
|
4333
|
+
}));
|
|
4334
|
+
let arr = graph.getRelatedEdgesData(t, 'in')
|
|
4335
|
+
.filter((d) => d.target === t && pos(d.source)[along] < pt[along])
|
|
4336
|
+
.map((d) => ({
|
|
4337
|
+
id: d.id,
|
|
4338
|
+
span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] -
|
|
4339
|
+
pt[across]),
|
|
4340
|
+
}));
|
|
4341
|
+
return {dep: elbow_rank(dep, edge.id), arr: elbow_rank(arr, edge.id)};
|
|
4342
|
+
} catch(e) {
|
|
4343
|
+
log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
|
|
4344
|
+
return null;
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
|
|
4300
4348
|
class TreedbElbowV extends Polyline
|
|
4301
4349
|
{
|
|
4302
4350
|
getControlPoints(attributes) {
|
|
4303
4351
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
4304
4352
|
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
|
|
4305
|
-
elbow_lane_of(this), true, elbow_obstacles(this)
|
|
4353
|
+
elbow_lane_of(this), true, elbow_obstacles(this),
|
|
4354
|
+
elbow_stagger_of(this, true));
|
|
4306
4355
|
}
|
|
4307
4356
|
|
|
4308
4357
|
getKeyPath(attributes) {
|
|
@@ -4316,7 +4365,8 @@ class TreedbElbowH extends Polyline
|
|
|
4316
4365
|
getControlPoints(attributes) {
|
|
4317
4366
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
4318
4367
|
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
|
|
4319
|
-
elbow_lane_of(this), false, elbow_obstacles(this)
|
|
4368
|
+
elbow_lane_of(this), false, elbow_obstacles(this),
|
|
4369
|
+
elbow_stagger_of(this, false));
|
|
4320
4370
|
}
|
|
4321
4371
|
|
|
4322
4372
|
getKeyPath(attributes) {
|
package/src/treedb_elbow.js
CHANGED
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
* the order the edges were made, so a new edge never moves the
|
|
26
26
|
* ones already drawn.
|
|
27
27
|
*
|
|
28
|
+
* - Several forward edges sharing one END: the children of one
|
|
29
|
+
* card, or the parents of one card (all its fkeys enter by one
|
|
30
|
+
* point). They are STAGGERED: each turns at its own height in
|
|
31
|
+
* the channel, so their runs lie side by side instead of on top
|
|
32
|
+
* of each other. Leaving a card, the one that runs farthest
|
|
33
|
+
* turns highest; reaching a card, the one that comes from
|
|
34
|
+
* farthest turns lowest -- the two orders in which the lines of
|
|
35
|
+
* one end nest without crossing.
|
|
36
|
+
*
|
|
28
37
|
* Written for the top-down reading; left to right is the same
|
|
29
38
|
* geometry with the axes swapped.
|
|
30
39
|
*
|
|
@@ -35,6 +44,7 @@
|
|
|
35
44
|
export const ELBOW_STEP = 10; /* between two lanes */
|
|
36
45
|
export const ELBOW_JETTY = 16; /* straight run out of a port before a detour turns */
|
|
37
46
|
export const ELBOW_CLEAR = 24; /* how far outside the two cards a detour runs */
|
|
47
|
+
export const ELBOW_STAGGER_PAD = 6; /* staggered turns keep this far from the rows */
|
|
38
48
|
|
|
39
49
|
/************************************************************
|
|
40
50
|
* The lane of `id` among `ids`, the edges joining the same two
|
|
@@ -54,6 +64,24 @@ export function elbow_lane(ids, id)
|
|
|
54
64
|
return side * Math.ceil(k / 2);
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
/************************************************************
|
|
68
|
+
* The rank of `id` among `items` ([{id, span}]): the forward
|
|
69
|
+
* edges that share one end with it, each with how far it runs
|
|
70
|
+
* ACROSS. Farthest first; a tie goes to the edge made first.
|
|
71
|
+
* Returns {rank, count}.
|
|
72
|
+
************************************************************/
|
|
73
|
+
export function elbow_rank(items, id)
|
|
74
|
+
{
|
|
75
|
+
let sorted = items.slice().sort((a, b) => {
|
|
76
|
+
if(b.span !== a.span) {
|
|
77
|
+
return b.span - a.span;
|
|
78
|
+
}
|
|
79
|
+
return String(a.id).localeCompare(String(b.id), undefined, {numeric: true});
|
|
80
|
+
});
|
|
81
|
+
let rank = sorted.findIndex((it) => it.id === id);
|
|
82
|
+
return {rank: (rank < 0)? 0 : rank, count: sorted.length};
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
function swap(p)
|
|
58
86
|
{
|
|
59
87
|
return [p[1], p[0]];
|
|
@@ -71,21 +99,44 @@ function swap_box(b)
|
|
|
71
99
|
*
|
|
72
100
|
* vertical the rows are stacked (top to bottom); false
|
|
73
101
|
* when they are side by side (left to right)
|
|
102
|
+
* stagger {dep, arr}: the edge's rank among the forward
|
|
103
|
+
* edges leaving its source card and among those
|
|
104
|
+
* reaching its target card (elbow_rank). Absent,
|
|
105
|
+
* the channel is the middle.
|
|
74
106
|
************************************************************/
|
|
75
|
-
export function elbow_points(s, t, box_s, box_t, lane, vertical)
|
|
107
|
+
export function elbow_points(s, t, box_s, box_t, lane, vertical, stagger)
|
|
76
108
|
{
|
|
77
109
|
if(!vertical) {
|
|
78
|
-
return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true
|
|
79
|
-
|
|
110
|
+
return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true,
|
|
111
|
+
stagger).map(swap);
|
|
80
112
|
}
|
|
81
113
|
|
|
82
114
|
let shift = lane * ELBOW_STEP;
|
|
83
115
|
|
|
84
|
-
/* Forward: the channel half way
|
|
85
|
-
*
|
|
116
|
+
/* Forward: the channel half way between the two cards, each
|
|
117
|
+
* edge of a shared end at its own height round it, moved by the
|
|
118
|
+
* lane, and kept between the two rows. */
|
|
86
119
|
if(t[1] - s[1] > 2 * ELBOW_STEP) {
|
|
87
|
-
let
|
|
88
|
-
|
|
120
|
+
let lo = Math.max(s[1], box_s.y2);
|
|
121
|
+
let hi = Math.min(t[1], box_t.y1);
|
|
122
|
+
let mid = (lo + hi) / 2;
|
|
123
|
+
let st = stagger || {};
|
|
124
|
+
let group = null;
|
|
125
|
+
let k = 0;
|
|
126
|
+
if(st.dep && st.dep.count > 1) {
|
|
127
|
+
group = st.dep;
|
|
128
|
+
k = st.dep.rank; /* farthest highest */
|
|
129
|
+
} else if(st.arr && st.arr.count > 1) {
|
|
130
|
+
group = st.arr;
|
|
131
|
+
k = st.arr.count - 1 - st.arr.rank; /* farthest lowest */
|
|
132
|
+
}
|
|
133
|
+
if(group) {
|
|
134
|
+
let room = Math.max(0, hi - lo - 2 * ELBOW_STAGGER_PAD);
|
|
135
|
+
let step = Math.min(ELBOW_STEP, room / (group.count - 1));
|
|
136
|
+
mid = mid - (group.count - 1) * step / 2 + k * step;
|
|
137
|
+
}
|
|
138
|
+
mid += shift;
|
|
139
|
+
mid = Math.min(Math.max(mid, lo + 4), hi - 4);
|
|
89
140
|
return [[s[0], mid], [t[0], mid]];
|
|
90
141
|
}
|
|
91
142
|
|
|
@@ -215,8 +266,12 @@ function heap_pop(h)
|
|
|
215
266
|
/************************************************************
|
|
216
267
|
* The shortest orthogonal line from `a` to `z` that enters none
|
|
217
268
|
* of `boxes` (already expanded by the clearance), a turn costing
|
|
218
|
-
* ROUTE_BEND. The line leaves `a`
|
|
219
|
-
*
|
|
269
|
+
* ROUTE_BEND. The line leaves `a` SIDEWAYS and reaches `z`
|
|
270
|
+
* SIDEWAYS: the column straight under a port and the one straight
|
|
271
|
+
* over it belong to the forward edges that share that port, which
|
|
272
|
+
* run along them to their staggered turns -- a route going down
|
|
273
|
+
* that column lay on top of them. So a routed line shares only
|
|
274
|
+
* the stub between the port and `a` (or `z`).
|
|
220
275
|
*
|
|
221
276
|
* The grid is SPARSE: only the lines of the boxes' borders and
|
|
222
277
|
* of the two ends, which is all an orthogonal shortest path ever
|
|
@@ -302,7 +357,10 @@ function orth_search(a, z, boxes)
|
|
|
302
357
|
let k = state >> 2;
|
|
303
358
|
let dir = state & 3;
|
|
304
359
|
if(k === goal) {
|
|
305
|
-
|
|
360
|
+
if(dir >= 2) {
|
|
361
|
+
continue; /* down the port's own column: not allowed */
|
|
362
|
+
}
|
|
363
|
+
let total = cost + ROUTE_BEND; /* the turn into the port */
|
|
306
364
|
if(total < found_cost) {
|
|
307
365
|
found_cost = total;
|
|
308
366
|
found = state;
|
|
@@ -310,6 +368,9 @@ function orth_search(a, z, boxes)
|
|
|
310
368
|
continue;
|
|
311
369
|
}
|
|
312
370
|
for(let [nk, nd, len] of moves(k)) {
|
|
371
|
+
if(k === start && nd >= 2) {
|
|
372
|
+
continue; /* out of the port sideways, not down its column */
|
|
373
|
+
}
|
|
313
374
|
let c = cost + len + ((nd === dir)? 0 : ROUTE_BEND);
|
|
314
375
|
let ns = nk * 4 + nd;
|
|
315
376
|
if(c < best[ns]) {
|
|
@@ -360,15 +421,15 @@ function orth_search(a, z, boxes)
|
|
|
360
421
|
* coincide. Nearby cards first; all of them if that finds no way;
|
|
361
422
|
* the simple elbow if nothing does.
|
|
362
423
|
************************************************************/
|
|
363
|
-
export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes)
|
|
424
|
+
export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger)
|
|
364
425
|
{
|
|
365
426
|
let others = boxes || [];
|
|
366
427
|
if(!vertical) {
|
|
367
428
|
return elbow_route(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true,
|
|
368
|
-
others.map(swap_box)).map(swap);
|
|
429
|
+
others.map(swap_box), stagger).map(swap);
|
|
369
430
|
}
|
|
370
431
|
|
|
371
|
-
let simple = elbow_points(s, t, box_s, box_t, lane, true);
|
|
432
|
+
let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
|
|
372
433
|
if(!elbow_path_hits([s, ...simple, t], others, box_s, box_t)) {
|
|
373
434
|
return simple;
|
|
374
435
|
}
|
package/src/treedb_elbow.test.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
***********************************************************************/
|
|
8
8
|
import { describe, test, expect } from "vitest";
|
|
9
9
|
import {
|
|
10
|
-
elbow_lane, elbow_points, elbow_route, elbow_path_hits, ELBOW_STEP, ELBOW_CLEAR,
|
|
10
|
+
elbow_lane, elbow_points, elbow_route, elbow_path_hits, elbow_rank, ELBOW_STEP, ELBOW_CLEAR,
|
|
11
11
|
} from "./treedb_elbow.js";
|
|
12
12
|
|
|
13
13
|
/* Two cards 100x40: A on top, B one row below it. */
|
|
@@ -165,6 +165,112 @@ describe("the route round the other cards", () => {
|
|
|
165
165
|
});
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
+
describe("edges sharing an end are staggered", () => {
|
|
169
|
+
test("the rank is farthest first, a tie to the edge made first", () => {
|
|
170
|
+
let items = [{id: "e-3", span: 10}, {id: "e-1", span: 300}, {id: "e-2", span: 10}];
|
|
171
|
+
expect(elbow_rank(items, "e-1")).toEqual({rank: 0, count: 3});
|
|
172
|
+
expect(elbow_rank(items, "e-2")).toEqual({rank: 1, count: 3});
|
|
173
|
+
expect(elbow_rank(items, "e-3")).toEqual({rank: 2, count: 3});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/* One parent on top (port at x 50, bottom at 40) and a row of
|
|
177
|
+
* children below (tops at 130). */
|
|
178
|
+
const P = {x1: 0, y1: 0, x2: 100, y2: 40};
|
|
179
|
+
const PORT = [50, 40];
|
|
180
|
+
const KIDS = [50, 160, 290, 420, -120].map((x, i) => ({
|
|
181
|
+
id: `e-${i + 1}`, x: x, box: {x1: x - 50, y1: 130, x2: x + 50, y2: 170},
|
|
182
|
+
}));
|
|
183
|
+
const channel_of = (kid) => {
|
|
184
|
+
let items = KIDS.map((k) => ({id: k.id, span: Math.abs(k.x - PORT[0])}));
|
|
185
|
+
let dep = elbow_rank(items, kid.id);
|
|
186
|
+
return elbow_points(PORT, [kid.x, 130], P, kid.box, 0, true, {dep: dep})[0][1];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
test("the children of one port turn each at its own height, inside the channel", () => {
|
|
190
|
+
let ys = KIDS.map(channel_of);
|
|
191
|
+
expect(new Set(ys).size).toBe(KIDS.length);
|
|
192
|
+
for(let y of ys) {
|
|
193
|
+
expect(y).toBeGreaterThan(40);
|
|
194
|
+
expect(y).toBeLessThan(130);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("the farthest turns highest, and the lines of one port do not cross", () => {
|
|
199
|
+
let y = Object.fromEntries(KIDS.map((k) => [k.id, channel_of(k)]));
|
|
200
|
+
expect(y["e-4"]).toBeLessThan(y["e-2"]); /* 420 is farther than 160 */
|
|
201
|
+
/* A crossing: one edge's drop (at its child's x, from its turn
|
|
202
|
+
* down to the row) cut by another's run (from the port to its
|
|
203
|
+
* child, at its own turn). */
|
|
204
|
+
for(let a of KIDS) {
|
|
205
|
+
for(let b of KIDS) {
|
|
206
|
+
if(a === b) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
let lo = Math.min(PORT[0], b.x);
|
|
210
|
+
let hi = Math.max(PORT[0], b.x);
|
|
211
|
+
let cuts = a.x > lo && a.x < hi && y[b.id] > y[a.id] && y[b.id] < 130;
|
|
212
|
+
expect(cuts).toBe(false);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("the parents of one card: the farthest turns lowest, and they do not cross", () => {
|
|
218
|
+
/* Parents on top at these x (port at the middle of each bottom),
|
|
219
|
+
* one child below at x 200 whose fkeys all enter at its top. */
|
|
220
|
+
const C = {x1: 150, y1: 130, x2: 250, y2: 170};
|
|
221
|
+
const T0 = [200, 130];
|
|
222
|
+
const PARENTS = [0, 120, 330, 560].map((x, i) => ({id: `p-${i + 1}`, x: x}));
|
|
223
|
+
let items = PARENTS.map((p) => ({id: p.id, span: Math.abs(p.x - T0[0])}));
|
|
224
|
+
let y = {};
|
|
225
|
+
for(let p of PARENTS) {
|
|
226
|
+
let box = {x1: p.x - 50, y1: 0, x2: p.x + 50, y2: 40};
|
|
227
|
+
let arr = elbow_rank(items, p.id);
|
|
228
|
+
y[p.id] = elbow_points([p.x, 40], T0, box, C, 0, true, {arr: arr})[0][1];
|
|
229
|
+
}
|
|
230
|
+
expect(new Set(Object.values(y)).size).toBe(PARENTS.length);
|
|
231
|
+
expect(y["p-4"]).toBeGreaterThan(y["p-2"]); /* 560 is farther than 120 */
|
|
232
|
+
/* A crossing: one parent's drop (at its x, from its row down to
|
|
233
|
+
* its turn) cut by another's run (from its x to the child). */
|
|
234
|
+
for(let a of PARENTS) {
|
|
235
|
+
for(let b of PARENTS) {
|
|
236
|
+
if(a === b) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
let lo = Math.min(b.x, T0[0]);
|
|
240
|
+
let hi = Math.max(b.x, T0[0]);
|
|
241
|
+
let cuts = a.x > lo && a.x < hi && y[b.id] > 40 && y[b.id] < y[a.id];
|
|
242
|
+
expect(cuts).toBe(false);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("a routed edge leaves and reaches its ports sideways, sharing only the stub", () => {
|
|
248
|
+
/* A same-row edge from A to C, with D standing between them:
|
|
249
|
+
* it is routed. It must not run down the column over C's
|
|
250
|
+
* port, which is where a forward edge into C drops. */
|
|
251
|
+
const A2 = {x1: 0, y1: 200, x2: 100, y2: 240};
|
|
252
|
+
const D2 = {x1: 150, y1: 180, x2: 250, y2: 260};
|
|
253
|
+
const C2 = {x1: 300, y1: 200, x2: 400, y2: 240};
|
|
254
|
+
let s = [50, 240];
|
|
255
|
+
let t = [350, 200];
|
|
256
|
+
let boxes = [A2, D2, C2];
|
|
257
|
+
let route = elbow_route(s, t, A2, C2, 0, true, boxes);
|
|
258
|
+
expect(route.length).toBeGreaterThan(2);
|
|
259
|
+
expect(elbow_path_hits([s, ...route, t], boxes, A2, C2)).toBe(false);
|
|
260
|
+
let a = route[0];
|
|
261
|
+
let z = route[route.length - 1];
|
|
262
|
+
expect(route[1][1]).toBe(a[1]); /* leaves a sideways */
|
|
263
|
+
expect(route[route.length - 2][1]).toBe(z[1]); /* reaches z sideways */
|
|
264
|
+
expect(t[1] - z[1]).toBeLessThan(20); /* the stub is short */
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("an end shared by nobody keeps the middle", () => {
|
|
268
|
+
let one = {rank: 0, count: 1};
|
|
269
|
+
expect(elbow_points(PORT, [160, 130], P, KIDS[1].box, 0, true, {dep: one, arr: one}))
|
|
270
|
+
.toEqual(elbow_points(PORT, [160, 130], P, KIDS[1].box, 0, true));
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
168
274
|
describe("the ports are obstacles too", () => {
|
|
169
275
|
/* Whole-node boxes: a port of 14px sticks 7px out of the bottom
|
|
170
276
|
* and the top of each card. */
|