@yuneta/gobj-ui 7.23.147 → 7.23.148
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 +15 -3
- package/dist/gobj-ui.cjs.js +211 -0
- package/dist/gobj-ui.es.js +211 -0
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +20 -1
- package/src/c_yui_treedb_graph.js +2 -0
- package/src/treedb_layout.js +258 -0
- package/src/treedb_layout.test.js +102 -1
package/README.md
CHANGED
|
@@ -1387,8 +1387,8 @@ G6 5.1 registers these layouts, and this is how each meets that shape:
|
|
|
1387
1387
|
| `circular`, `concentric`, `grid`, `mds`, `random` | rings, rings by degree, a grid, projection, noise | none knows a parent from a child |
|
|
1388
1388
|
| `combo-combined`, `fishbone` | combos / cause-effect | not this data |
|
|
1389
1389
|
|
|
1390
|
-
So
|
|
1391
|
-
layouts by
|
|
1390
|
+
So three of our own, in `treedb_layout.js` (pure, tested), registered as G6
|
|
1391
|
+
layouts by three thin adapters in `c_g6_nodes_tree.js`:
|
|
1392
1392
|
|
|
1393
1393
|
- **`treedb-tree`** — the classic tidy tree, **top to bottom**. A row per
|
|
1394
1394
|
depth as tall as its tallest card, a node centred over the block of its
|
|
@@ -1402,6 +1402,18 @@ layouts by two thin adapters in `c_g6_nodes_tree.js`:
|
|
|
1402
1402
|
keeps the other reading for a host that wants it). The outline that shipped
|
|
1403
1403
|
beside it (`treedb-outline`, one row per node) was removed in `7.23.75`: a
|
|
1404
1404
|
list that indents is a JSON viewer, and this library already has one.
|
|
1405
|
+
- **`compact-tree`** (`treedb-compact`, `7.23.148`) — the same tree packed by
|
|
1406
|
+
**contour** instead of by block. The tidy tree gives each subtree its whole
|
|
1407
|
+
block, so a hall opened with twenty-four devices pushes the siblings of its
|
|
1408
|
+
ancestors apart at depths where nothing of theirs touches; here every
|
|
1409
|
+
sibling slides against the outline of the ones before it, and a closed
|
|
1410
|
+
branch sits beside an open one at the depth they share. Same rows and order
|
|
1411
|
+
as `treedb-tree`, so switching moves cards sideways and never reorders them.
|
|
1412
|
+
One region open with a 24-device hall is 70% of the tidy width on a treedb
|
|
1413
|
+
shaped like a real one; a uniform tree is the same width. The contour
|
|
1414
|
+
arithmetic is Moen's (1990) as mxGraph wrote it in `mxCompactTreeLayout`
|
|
1415
|
+
(JGraph Ltd, Apache-2.0), read in maxGraph — a port, not a dependency,
|
|
1416
|
+
because maxGraph's layouts drive its own graph model.
|
|
1405
1417
|
- **`radial`** (`treedb-radial`) — the root in the middle, a ring per depth,
|
|
1406
1418
|
every subtree an angular **sector** proportional to its leaves, and the
|
|
1407
1419
|
radius of each ring the largest of three: one `ranksep` out from the ring
|
|
@@ -1413,7 +1425,7 @@ layouts by two thin adapters in `c_g6_nodes_tree.js`:
|
|
|
1413
1425
|
construction, across the ring or along the radius; several roots share the
|
|
1414
1426
|
circle around an empty centre.
|
|
1415
1427
|
|
|
1416
|
-
|
|
1428
|
+
All three take the **same spanning tree**, chosen deterministically: roots are the
|
|
1417
1429
|
nodes with no incoming edge, in node order; a node belongs to the **first
|
|
1418
1430
|
parent that reaches it** in a breadth-first walk (the place, not the
|
|
1419
1431
|
controller, because the place's column came first) and its other links are
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -29314,6 +29314,203 @@ function tidy_tree_lr(nodes, edges, o) {
|
|
|
29314
29314
|
}
|
|
29315
29315
|
return pos;
|
|
29316
29316
|
}
|
|
29317
|
+
/************************************************************
|
|
29318
|
+
* The compact tree.
|
|
29319
|
+
*
|
|
29320
|
+
* The tidy tree above gives every subtree its whole BLOCK: open a
|
|
29321
|
+
* hall with twenty-four devices and its block widens, and with it
|
|
29322
|
+
* every block above it, so the siblings of its ancestors move apart
|
|
29323
|
+
* at depths where nothing of theirs touches. This one keeps the
|
|
29324
|
+
* CONTOUR of each subtree instead -- its outline on each side, depth
|
|
29325
|
+
* by depth -- and slides every sibling against the outline of the
|
|
29326
|
+
* ones before it. A folded region then sits as close to an open one
|
|
29327
|
+
* as their cards allow at each depth, which is what a folded treedb
|
|
29328
|
+
* is: one branch open among closed ones.
|
|
29329
|
+
*
|
|
29330
|
+
* Same spanning tree, same order, same rows as the tidy tree: only
|
|
29331
|
+
* the packing across the depths differs, so switching between the
|
|
29332
|
+
* two moves cards sideways and never reorders them.
|
|
29333
|
+
*
|
|
29334
|
+
* The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
|
|
29335
|
+
* Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
|
|
29336
|
+
* -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
|
|
29337
|
+
* maxGraph's CompactTreeLayout.ts: leaf, join, merge, offset and
|
|
29338
|
+
* bridge below follow that form. What is ours: it runs on the
|
|
29339
|
+
* spanning tree of a FOREST, every depth is a row as wide as its
|
|
29340
|
+
* widest card (mxGraph's `alignRanks`), several roots sit side by
|
|
29341
|
+
* side, and there is no recursion.
|
|
29342
|
+
************************************************************/
|
|
29343
|
+
function layout_compact(nodes, edges, opts) {
|
|
29344
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
29345
|
+
if (o.direction === "LR") return compact_tree_lr(nodes, edges, o);
|
|
29346
|
+
let lr = compact_tree_lr(nodes.map((n) => Object.assign({}, n, {
|
|
29347
|
+
w: n.h,
|
|
29348
|
+
h: n.w
|
|
29349
|
+
})), edges, o);
|
|
29350
|
+
let pos = /* @__PURE__ */ new Map();
|
|
29351
|
+
for (let [id, p] of lr) pos.set(id, {
|
|
29352
|
+
x: p.y,
|
|
29353
|
+
y: p.x
|
|
29354
|
+
});
|
|
29355
|
+
return pos;
|
|
29356
|
+
}
|
|
29357
|
+
function contour_line(dx, dy, next) {
|
|
29358
|
+
return {
|
|
29359
|
+
dx,
|
|
29360
|
+
dy,
|
|
29361
|
+
next: next || null
|
|
29362
|
+
};
|
|
29363
|
+
}
|
|
29364
|
+
function contour_offset(p1, p2, a1, a2, b1, b2) {
|
|
29365
|
+
if (b1 <= p1 || p1 + a1 <= 0) return 0;
|
|
29366
|
+
let d;
|
|
29367
|
+
if (b1 * a2 - a1 * b2 > 0) {
|
|
29368
|
+
if (p1 < 0) d = p1 * a2 / a1 - p2;
|
|
29369
|
+
else if (p1 > 0) d = p1 * b2 / b1 - p2;
|
|
29370
|
+
else d = -p2;
|
|
29371
|
+
} else if (b1 < p1 + a1) d = b2 - (p2 + (b1 - p1) * a2 / a1);
|
|
29372
|
+
else if (b1 > p1 + a1) d = (a1 + p1) * b2 / b1 - (p2 + a2);
|
|
29373
|
+
else d = b2 - (p2 + a2);
|
|
29374
|
+
return d > 0 ? d : 0;
|
|
29375
|
+
}
|
|
29376
|
+
function contour_bridge(line1, x1, y1, line2, x2, y2) {
|
|
29377
|
+
let dx = x2 + line2.dx - x1;
|
|
29378
|
+
let dy = line2.dx === 0 ? line2.dy : dx * line2.dy / line2.dx;
|
|
29379
|
+
let r = contour_line(dx, dy, line2.next);
|
|
29380
|
+
line1.next = contour_line(0, y2 + line2.dy - dy - y1, r);
|
|
29381
|
+
return r;
|
|
29382
|
+
}
|
|
29383
|
+
function contour_merge(p1, p2) {
|
|
29384
|
+
let x = 0;
|
|
29385
|
+
let y = 0;
|
|
29386
|
+
let total = 0;
|
|
29387
|
+
let upper = p1.lower_head;
|
|
29388
|
+
let lower = p2.upper_head;
|
|
29389
|
+
while (lower && upper) {
|
|
29390
|
+
let d = contour_offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
|
|
29391
|
+
y += d;
|
|
29392
|
+
total += d;
|
|
29393
|
+
if (x + lower.dx <= upper.dx) {
|
|
29394
|
+
x += lower.dx;
|
|
29395
|
+
y += lower.dy;
|
|
29396
|
+
lower = lower.next;
|
|
29397
|
+
} else {
|
|
29398
|
+
x -= upper.dx;
|
|
29399
|
+
y -= upper.dy;
|
|
29400
|
+
upper = upper.next;
|
|
29401
|
+
}
|
|
29402
|
+
}
|
|
29403
|
+
if (lower) {
|
|
29404
|
+
let b = contour_bridge(p1.upper_tail, 0, 0, lower, x, y);
|
|
29405
|
+
p1.upper_tail = b.next ? p2.upper_tail : b;
|
|
29406
|
+
p1.lower_tail = p2.lower_tail;
|
|
29407
|
+
} else {
|
|
29408
|
+
let b = contour_bridge(p2.lower_tail, x, y, upper, 0, 0);
|
|
29409
|
+
if (!b.next) p1.lower_tail = b;
|
|
29410
|
+
}
|
|
29411
|
+
p1.lower_head = p2.lower_head;
|
|
29412
|
+
return total;
|
|
29413
|
+
}
|
|
29414
|
+
function compact_tree_lr(nodes, edges, o) {
|
|
29415
|
+
let t = spanning_tree(nodes, edges);
|
|
29416
|
+
let order = walk_order(t);
|
|
29417
|
+
let pos = /* @__PURE__ */ new Map();
|
|
29418
|
+
let col_w = [];
|
|
29419
|
+
for (let [id, d] of t.depth) col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
|
|
29420
|
+
let col_x = [];
|
|
29421
|
+
let x = 0;
|
|
29422
|
+
for (let d = 0; d < col_w.length; d++) {
|
|
29423
|
+
col_x[d] = x;
|
|
29424
|
+
x += (col_w[d] || 0) + o.ranksep;
|
|
29425
|
+
}
|
|
29426
|
+
let pad = o.nodesep / 2;
|
|
29427
|
+
let dist = o.nodesep;
|
|
29428
|
+
let level_distance = o.ranksep - pad;
|
|
29429
|
+
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
|
+
});
|
|
29438
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
29439
|
+
let id = order[i];
|
|
29440
|
+
let n = tn.get(id);
|
|
29441
|
+
let kids = t.children.get(id) || [];
|
|
29442
|
+
if (!kids.length) {
|
|
29443
|
+
let upper = contour_line(n.len + dist, 0);
|
|
29444
|
+
let lower_tail = contour_line(0, -n.ext - dist);
|
|
29445
|
+
n.contour = {
|
|
29446
|
+
upper_head: upper,
|
|
29447
|
+
upper_tail: upper,
|
|
29448
|
+
lower_tail,
|
|
29449
|
+
lower_head: contour_line(n.len + dist, 0, lower_tail)
|
|
29450
|
+
};
|
|
29451
|
+
continue;
|
|
29452
|
+
}
|
|
29453
|
+
let first = tn.get(kids[0]);
|
|
29454
|
+
n.contour = first.contour;
|
|
29455
|
+
let h = first.ext + dist;
|
|
29456
|
+
let span = h;
|
|
29457
|
+
for (let k = 1; k < kids.length; k++) {
|
|
29458
|
+
let kid = tn.get(kids[k]);
|
|
29459
|
+
let d = contour_merge(n.contour, kid.contour);
|
|
29460
|
+
kid.off_y = d + h;
|
|
29461
|
+
kid.off_x = 0;
|
|
29462
|
+
h = kid.ext + dist;
|
|
29463
|
+
span += d + h;
|
|
29464
|
+
}
|
|
29465
|
+
let gap = pad + level_distance;
|
|
29466
|
+
let y2 = (span - n.ext) / 2 - pad;
|
|
29467
|
+
let y1 = y2 + n.ext + 2 * pad - span;
|
|
29468
|
+
first.off_x = gap + n.len;
|
|
29469
|
+
first.off_y = y1;
|
|
29470
|
+
n.contour.upper_head = contour_line(n.len, 0, contour_line(gap, y1, n.contour.upper_head));
|
|
29471
|
+
n.contour.lower_head = contour_line(n.len, 0, contour_line(gap, y2, n.contour.lower_head));
|
|
29472
|
+
}
|
|
29473
|
+
let root_of = /* @__PURE__ */ new Map();
|
|
29474
|
+
for (let id of order) {
|
|
29475
|
+
let n = tn.get(id);
|
|
29476
|
+
if (!root_of.has(id)) root_of.set(id, id);
|
|
29477
|
+
let kids = t.children.get(id) || [];
|
|
29478
|
+
if (!kids.length) continue;
|
|
29479
|
+
let running = n.y + tn.get(kids[0]).off_y;
|
|
29480
|
+
tn.get(kids[0]).y = running;
|
|
29481
|
+
root_of.set(kids[0], root_of.get(id));
|
|
29482
|
+
for (let k = 1; k < kids.length; k++) {
|
|
29483
|
+
let kid = tn.get(kids[k]);
|
|
29484
|
+
kid.y = running + kid.off_y;
|
|
29485
|
+
running = kid.y;
|
|
29486
|
+
root_of.set(kids[k], root_of.get(id));
|
|
29487
|
+
}
|
|
29488
|
+
}
|
|
29489
|
+
let lo = /* @__PURE__ */ new Map();
|
|
29490
|
+
let hi = /* @__PURE__ */ new Map();
|
|
29491
|
+
for (let id of order) {
|
|
29492
|
+
let r = root_of.get(id);
|
|
29493
|
+
let n = tn.get(id);
|
|
29494
|
+
lo.set(r, Math.min(lo.has(r) ? lo.get(r) : Infinity, n.y));
|
|
29495
|
+
hi.set(r, Math.max(hi.has(r) ? hi.get(r) : -Infinity, n.y + n.ext));
|
|
29496
|
+
}
|
|
29497
|
+
let shift = /* @__PURE__ */ new Map();
|
|
29498
|
+
let top = 0;
|
|
29499
|
+
for (let r of t.roots) {
|
|
29500
|
+
shift.set(r, top - lo.get(r));
|
|
29501
|
+
top += hi.get(r) - lo.get(r) + o.nodesep * 2;
|
|
29502
|
+
}
|
|
29503
|
+
for (let id of order) {
|
|
29504
|
+
let d = t.depth.get(id);
|
|
29505
|
+
let n = tn.get(id);
|
|
29506
|
+
let y = n.y + shift.get(root_of.get(id));
|
|
29507
|
+
pos.set(id, {
|
|
29508
|
+
x: col_x[d] + col_w[d] / 2,
|
|
29509
|
+
y: y + n.ext / 2
|
|
29510
|
+
});
|
|
29511
|
+
}
|
|
29512
|
+
return pos;
|
|
29513
|
+
}
|
|
29317
29514
|
function extent_at(n, a) {
|
|
29318
29515
|
if (!n) return 0;
|
|
29319
29516
|
return Math.abs(n.w * Math.cos(a)) + Math.abs(n.h * Math.sin(a));
|
|
@@ -56297,6 +56494,12 @@ var _layouts = {
|
|
|
56297
56494
|
nodesep: 18,
|
|
56298
56495
|
ranksep: 90
|
|
56299
56496
|
},
|
|
56497
|
+
"compact-tree": {
|
|
56498
|
+
type: "treedb-compact",
|
|
56499
|
+
direction: "TB",
|
|
56500
|
+
nodesep: 18,
|
|
56501
|
+
ranksep: 90
|
|
56502
|
+
},
|
|
56300
56503
|
"dagre": {
|
|
56301
56504
|
type: "dagre",
|
|
56302
56505
|
rankdir: "LR",
|
|
@@ -56618,6 +56821,7 @@ function register_layouts(gobj) {
|
|
|
56618
56821
|
_g6_extensions_registered = true;
|
|
56619
56822
|
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "manual", ManualLayout);
|
|
56620
56823
|
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
|
|
56824
|
+
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
|
|
56621
56825
|
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
|
|
56622
56826
|
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "light", LightNode);
|
|
56623
56827
|
(0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "treedb-card", TreedbCard);
|
|
@@ -58785,6 +58989,12 @@ var TreedbTreeLayout = class extends _antv_g6.BaseLayout {
|
|
|
58785
58989
|
return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
|
|
58786
58990
|
}
|
|
58787
58991
|
};
|
|
58992
|
+
var TreedbCompactLayout = class extends _antv_g6.BaseLayout {
|
|
58993
|
+
async execute(data, options) {
|
|
58994
|
+
let input = treedb_layout_input(data);
|
|
58995
|
+
return treedb_layout_output(layout_compact(input.nodes, input.edges, options));
|
|
58996
|
+
}
|
|
58997
|
+
};
|
|
58788
58998
|
var TreedbRadialLayout = class extends _antv_g6.BaseLayout {
|
|
58789
58999
|
async execute(data, options) {
|
|
58790
59000
|
let input = treedb_layout_input(data);
|
|
@@ -64256,6 +64466,7 @@ function option_label(name) {
|
|
|
64256
64466
|
case "edition": return (0, i18next.t)("edition");
|
|
64257
64467
|
case "manual": return (0, i18next.t)("manual");
|
|
64258
64468
|
case "treedb-tree": return (0, i18next.t)("treedb-tree");
|
|
64469
|
+
case "compact-tree": return (0, i18next.t)("compact-tree");
|
|
64259
64470
|
case "radial": return (0, i18next.t)("radial");
|
|
64260
64471
|
case "dagre": return (0, i18next.t)("dagre");
|
|
64261
64472
|
case "antv-dagre": return (0, i18next.t)("antv-dagre");
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -29309,6 +29309,203 @@ function tidy_tree_lr(nodes, edges, o) {
|
|
|
29309
29309
|
}
|
|
29310
29310
|
return pos;
|
|
29311
29311
|
}
|
|
29312
|
+
/************************************************************
|
|
29313
|
+
* The compact tree.
|
|
29314
|
+
*
|
|
29315
|
+
* The tidy tree above gives every subtree its whole BLOCK: open a
|
|
29316
|
+
* hall with twenty-four devices and its block widens, and with it
|
|
29317
|
+
* every block above it, so the siblings of its ancestors move apart
|
|
29318
|
+
* at depths where nothing of theirs touches. This one keeps the
|
|
29319
|
+
* CONTOUR of each subtree instead -- its outline on each side, depth
|
|
29320
|
+
* by depth -- and slides every sibling against the outline of the
|
|
29321
|
+
* ones before it. A folded region then sits as close to an open one
|
|
29322
|
+
* as their cards allow at each depth, which is what a folded treedb
|
|
29323
|
+
* is: one branch open among closed ones.
|
|
29324
|
+
*
|
|
29325
|
+
* Same spanning tree, same order, same rows as the tidy tree: only
|
|
29326
|
+
* the packing across the depths differs, so switching between the
|
|
29327
|
+
* two moves cards sideways and never reorders them.
|
|
29328
|
+
*
|
|
29329
|
+
* The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
|
|
29330
|
+
* Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
|
|
29331
|
+
* -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
|
|
29332
|
+
* maxGraph's CompactTreeLayout.ts: leaf, join, merge, offset and
|
|
29333
|
+
* bridge below follow that form. What is ours: it runs on the
|
|
29334
|
+
* spanning tree of a FOREST, every depth is a row as wide as its
|
|
29335
|
+
* widest card (mxGraph's `alignRanks`), several roots sit side by
|
|
29336
|
+
* side, and there is no recursion.
|
|
29337
|
+
************************************************************/
|
|
29338
|
+
function layout_compact(nodes, edges, opts) {
|
|
29339
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
29340
|
+
if (o.direction === "LR") return compact_tree_lr(nodes, edges, o);
|
|
29341
|
+
let lr = compact_tree_lr(nodes.map((n) => Object.assign({}, n, {
|
|
29342
|
+
w: n.h,
|
|
29343
|
+
h: n.w
|
|
29344
|
+
})), edges, o);
|
|
29345
|
+
let pos = /* @__PURE__ */ new Map();
|
|
29346
|
+
for (let [id, p] of lr) pos.set(id, {
|
|
29347
|
+
x: p.y,
|
|
29348
|
+
y: p.x
|
|
29349
|
+
});
|
|
29350
|
+
return pos;
|
|
29351
|
+
}
|
|
29352
|
+
function contour_line(dx, dy, next) {
|
|
29353
|
+
return {
|
|
29354
|
+
dx,
|
|
29355
|
+
dy,
|
|
29356
|
+
next: next || null
|
|
29357
|
+
};
|
|
29358
|
+
}
|
|
29359
|
+
function contour_offset(p1, p2, a1, a2, b1, b2) {
|
|
29360
|
+
if (b1 <= p1 || p1 + a1 <= 0) return 0;
|
|
29361
|
+
let d;
|
|
29362
|
+
if (b1 * a2 - a1 * b2 > 0) {
|
|
29363
|
+
if (p1 < 0) d = p1 * a2 / a1 - p2;
|
|
29364
|
+
else if (p1 > 0) d = p1 * b2 / b1 - p2;
|
|
29365
|
+
else d = -p2;
|
|
29366
|
+
} else if (b1 < p1 + a1) d = b2 - (p2 + (b1 - p1) * a2 / a1);
|
|
29367
|
+
else if (b1 > p1 + a1) d = (a1 + p1) * b2 / b1 - (p2 + a2);
|
|
29368
|
+
else d = b2 - (p2 + a2);
|
|
29369
|
+
return d > 0 ? d : 0;
|
|
29370
|
+
}
|
|
29371
|
+
function contour_bridge(line1, x1, y1, line2, x2, y2) {
|
|
29372
|
+
let dx = x2 + line2.dx - x1;
|
|
29373
|
+
let dy = line2.dx === 0 ? line2.dy : dx * line2.dy / line2.dx;
|
|
29374
|
+
let r = contour_line(dx, dy, line2.next);
|
|
29375
|
+
line1.next = contour_line(0, y2 + line2.dy - dy - y1, r);
|
|
29376
|
+
return r;
|
|
29377
|
+
}
|
|
29378
|
+
function contour_merge(p1, p2) {
|
|
29379
|
+
let x = 0;
|
|
29380
|
+
let y = 0;
|
|
29381
|
+
let total = 0;
|
|
29382
|
+
let upper = p1.lower_head;
|
|
29383
|
+
let lower = p2.upper_head;
|
|
29384
|
+
while (lower && upper) {
|
|
29385
|
+
let d = contour_offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
|
|
29386
|
+
y += d;
|
|
29387
|
+
total += d;
|
|
29388
|
+
if (x + lower.dx <= upper.dx) {
|
|
29389
|
+
x += lower.dx;
|
|
29390
|
+
y += lower.dy;
|
|
29391
|
+
lower = lower.next;
|
|
29392
|
+
} else {
|
|
29393
|
+
x -= upper.dx;
|
|
29394
|
+
y -= upper.dy;
|
|
29395
|
+
upper = upper.next;
|
|
29396
|
+
}
|
|
29397
|
+
}
|
|
29398
|
+
if (lower) {
|
|
29399
|
+
let b = contour_bridge(p1.upper_tail, 0, 0, lower, x, y);
|
|
29400
|
+
p1.upper_tail = b.next ? p2.upper_tail : b;
|
|
29401
|
+
p1.lower_tail = p2.lower_tail;
|
|
29402
|
+
} else {
|
|
29403
|
+
let b = contour_bridge(p2.lower_tail, x, y, upper, 0, 0);
|
|
29404
|
+
if (!b.next) p1.lower_tail = b;
|
|
29405
|
+
}
|
|
29406
|
+
p1.lower_head = p2.lower_head;
|
|
29407
|
+
return total;
|
|
29408
|
+
}
|
|
29409
|
+
function compact_tree_lr(nodes, edges, o) {
|
|
29410
|
+
let t = spanning_tree(nodes, edges);
|
|
29411
|
+
let order = walk_order(t);
|
|
29412
|
+
let pos = /* @__PURE__ */ new Map();
|
|
29413
|
+
let col_w = [];
|
|
29414
|
+
for (let [id, d] of t.depth) col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
|
|
29415
|
+
let col_x = [];
|
|
29416
|
+
let x = 0;
|
|
29417
|
+
for (let d = 0; d < col_w.length; d++) {
|
|
29418
|
+
col_x[d] = x;
|
|
29419
|
+
x += (col_w[d] || 0) + o.ranksep;
|
|
29420
|
+
}
|
|
29421
|
+
let pad = o.nodesep / 2;
|
|
29422
|
+
let dist = o.nodesep;
|
|
29423
|
+
let level_distance = o.ranksep - pad;
|
|
29424
|
+
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
|
+
});
|
|
29433
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
29434
|
+
let id = order[i];
|
|
29435
|
+
let n = tn.get(id);
|
|
29436
|
+
let kids = t.children.get(id) || [];
|
|
29437
|
+
if (!kids.length) {
|
|
29438
|
+
let upper = contour_line(n.len + dist, 0);
|
|
29439
|
+
let lower_tail = contour_line(0, -n.ext - dist);
|
|
29440
|
+
n.contour = {
|
|
29441
|
+
upper_head: upper,
|
|
29442
|
+
upper_tail: upper,
|
|
29443
|
+
lower_tail,
|
|
29444
|
+
lower_head: contour_line(n.len + dist, 0, lower_tail)
|
|
29445
|
+
};
|
|
29446
|
+
continue;
|
|
29447
|
+
}
|
|
29448
|
+
let first = tn.get(kids[0]);
|
|
29449
|
+
n.contour = first.contour;
|
|
29450
|
+
let h = first.ext + dist;
|
|
29451
|
+
let span = h;
|
|
29452
|
+
for (let k = 1; k < kids.length; k++) {
|
|
29453
|
+
let kid = tn.get(kids[k]);
|
|
29454
|
+
let d = contour_merge(n.contour, kid.contour);
|
|
29455
|
+
kid.off_y = d + h;
|
|
29456
|
+
kid.off_x = 0;
|
|
29457
|
+
h = kid.ext + dist;
|
|
29458
|
+
span += d + h;
|
|
29459
|
+
}
|
|
29460
|
+
let gap = pad + level_distance;
|
|
29461
|
+
let y2 = (span - n.ext) / 2 - pad;
|
|
29462
|
+
let y1 = y2 + n.ext + 2 * pad - span;
|
|
29463
|
+
first.off_x = gap + n.len;
|
|
29464
|
+
first.off_y = y1;
|
|
29465
|
+
n.contour.upper_head = contour_line(n.len, 0, contour_line(gap, y1, n.contour.upper_head));
|
|
29466
|
+
n.contour.lower_head = contour_line(n.len, 0, contour_line(gap, y2, n.contour.lower_head));
|
|
29467
|
+
}
|
|
29468
|
+
let root_of = /* @__PURE__ */ new Map();
|
|
29469
|
+
for (let id of order) {
|
|
29470
|
+
let n = tn.get(id);
|
|
29471
|
+
if (!root_of.has(id)) root_of.set(id, id);
|
|
29472
|
+
let kids = t.children.get(id) || [];
|
|
29473
|
+
if (!kids.length) continue;
|
|
29474
|
+
let running = n.y + tn.get(kids[0]).off_y;
|
|
29475
|
+
tn.get(kids[0]).y = running;
|
|
29476
|
+
root_of.set(kids[0], root_of.get(id));
|
|
29477
|
+
for (let k = 1; k < kids.length; k++) {
|
|
29478
|
+
let kid = tn.get(kids[k]);
|
|
29479
|
+
kid.y = running + kid.off_y;
|
|
29480
|
+
running = kid.y;
|
|
29481
|
+
root_of.set(kids[k], root_of.get(id));
|
|
29482
|
+
}
|
|
29483
|
+
}
|
|
29484
|
+
let lo = /* @__PURE__ */ new Map();
|
|
29485
|
+
let hi = /* @__PURE__ */ new Map();
|
|
29486
|
+
for (let id of order) {
|
|
29487
|
+
let r = root_of.get(id);
|
|
29488
|
+
let n = tn.get(id);
|
|
29489
|
+
lo.set(r, Math.min(lo.has(r) ? lo.get(r) : Infinity, n.y));
|
|
29490
|
+
hi.set(r, Math.max(hi.has(r) ? hi.get(r) : -Infinity, n.y + n.ext));
|
|
29491
|
+
}
|
|
29492
|
+
let shift = /* @__PURE__ */ new Map();
|
|
29493
|
+
let top = 0;
|
|
29494
|
+
for (let r of t.roots) {
|
|
29495
|
+
shift.set(r, top - lo.get(r));
|
|
29496
|
+
top += hi.get(r) - lo.get(r) + o.nodesep * 2;
|
|
29497
|
+
}
|
|
29498
|
+
for (let id of order) {
|
|
29499
|
+
let d = t.depth.get(id);
|
|
29500
|
+
let n = tn.get(id);
|
|
29501
|
+
let y = n.y + shift.get(root_of.get(id));
|
|
29502
|
+
pos.set(id, {
|
|
29503
|
+
x: col_x[d] + col_w[d] / 2,
|
|
29504
|
+
y: y + n.ext / 2
|
|
29505
|
+
});
|
|
29506
|
+
}
|
|
29507
|
+
return pos;
|
|
29508
|
+
}
|
|
29312
29509
|
function extent_at(n, a) {
|
|
29313
29510
|
if (!n) return 0;
|
|
29314
29511
|
return Math.abs(n.w * Math.cos(a)) + Math.abs(n.h * Math.sin(a));
|
|
@@ -56292,6 +56489,12 @@ var _layouts = {
|
|
|
56292
56489
|
nodesep: 18,
|
|
56293
56490
|
ranksep: 90
|
|
56294
56491
|
},
|
|
56492
|
+
"compact-tree": {
|
|
56493
|
+
type: "treedb-compact",
|
|
56494
|
+
direction: "TB",
|
|
56495
|
+
nodesep: 18,
|
|
56496
|
+
ranksep: 90
|
|
56497
|
+
},
|
|
56295
56498
|
"dagre": {
|
|
56296
56499
|
type: "dagre",
|
|
56297
56500
|
rankdir: "LR",
|
|
@@ -56613,6 +56816,7 @@ function register_layouts(gobj) {
|
|
|
56613
56816
|
_g6_extensions_registered = true;
|
|
56614
56817
|
register(ExtensionCategory.LAYOUT, "manual", ManualLayout);
|
|
56615
56818
|
register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
|
|
56819
|
+
register(ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
|
|
56616
56820
|
register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
|
|
56617
56821
|
register(ExtensionCategory.NODE, "light", LightNode);
|
|
56618
56822
|
register(ExtensionCategory.NODE, "treedb-card", TreedbCard);
|
|
@@ -58780,6 +58984,12 @@ var TreedbTreeLayout = class extends BaseLayout {
|
|
|
58780
58984
|
return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
|
|
58781
58985
|
}
|
|
58782
58986
|
};
|
|
58987
|
+
var TreedbCompactLayout = class extends BaseLayout {
|
|
58988
|
+
async execute(data, options) {
|
|
58989
|
+
let input = treedb_layout_input(data);
|
|
58990
|
+
return treedb_layout_output(layout_compact(input.nodes, input.edges, options));
|
|
58991
|
+
}
|
|
58992
|
+
};
|
|
58783
58993
|
var TreedbRadialLayout = class extends BaseLayout {
|
|
58784
58994
|
async execute(data, options) {
|
|
58785
58995
|
let input = treedb_layout_input(data);
|
|
@@ -64251,6 +64461,7 @@ function option_label(name) {
|
|
|
64251
64461
|
case "edition": return t("edition");
|
|
64252
64462
|
case "manual": return t("manual");
|
|
64253
64463
|
case "treedb-tree": return t("treedb-tree");
|
|
64464
|
+
case "compact-tree": return t("compact-tree");
|
|
64254
64465
|
case "radial": return t("radial");
|
|
64255
64466
|
case "dagre": return t("dagre");
|
|
64256
64467
|
case "antv-dagre": return t("antv-dagre");
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -132,7 +132,7 @@ import {
|
|
|
132
132
|
fold_split_group_key,
|
|
133
133
|
fold_root_group_key,
|
|
134
134
|
} from "./treedb_fold_model.js";
|
|
135
|
-
import {layout_tree, layout_radial} from "./treedb_layout.js";
|
|
135
|
+
import {layout_tree, layout_radial, layout_compact} from "./treedb_layout.js";
|
|
136
136
|
|
|
137
137
|
import {
|
|
138
138
|
BaseLayout,
|
|
@@ -350,6 +350,16 @@ const _layouts = {
|
|
|
350
350
|
nodesep: 18,
|
|
351
351
|
ranksep: 90,
|
|
352
352
|
},
|
|
353
|
+
/* The same tree packed by CONTOUR (Moen, as mxGraph drew it): a
|
|
354
|
+
* closed branch sits beside an open one at the depth they share,
|
|
355
|
+
* instead of beside the whole block of the open one. Same rows
|
|
356
|
+
* and order as `treedb-tree` (treedb_layout.js). */
|
|
357
|
+
"compact-tree": {
|
|
358
|
+
type: 'treedb-compact',
|
|
359
|
+
direction: 'TB',
|
|
360
|
+
nodesep: 18,
|
|
361
|
+
ranksep: 90,
|
|
362
|
+
},
|
|
353
363
|
"dagre": {
|
|
354
364
|
type: 'dagre',
|
|
355
365
|
rankdir: 'LR',
|
|
@@ -925,6 +935,7 @@ function register_layouts(gobj)
|
|
|
925
935
|
_g6_extensions_registered = true;
|
|
926
936
|
register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
|
|
927
937
|
register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
|
|
938
|
+
register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
|
|
928
939
|
register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
|
|
929
940
|
register(ExtensionCategory.NODE, 'light', LightNode);
|
|
930
941
|
register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
|
|
@@ -4188,6 +4199,14 @@ class TreedbTreeLayout extends BaseLayout
|
|
|
4188
4199
|
}
|
|
4189
4200
|
}
|
|
4190
4201
|
|
|
4202
|
+
class TreedbCompactLayout extends BaseLayout
|
|
4203
|
+
{
|
|
4204
|
+
async execute(data, options) {
|
|
4205
|
+
let input = treedb_layout_input(data);
|
|
4206
|
+
return treedb_layout_output(layout_compact(input.nodes, input.edges, options));
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4191
4210
|
class TreedbRadialLayout extends BaseLayout
|
|
4192
4211
|
{
|
|
4193
4212
|
async execute(data, options) {
|
package/src/treedb_layout.js
CHANGED
|
@@ -274,6 +274,264 @@ function tidy_tree_lr(nodes, edges, o)
|
|
|
274
274
|
return pos;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/************************************************************
|
|
278
|
+
* The compact tree.
|
|
279
|
+
*
|
|
280
|
+
* The tidy tree above gives every subtree its whole BLOCK: open a
|
|
281
|
+
* hall with twenty-four devices and its block widens, and with it
|
|
282
|
+
* every block above it, so the siblings of its ancestors move apart
|
|
283
|
+
* at depths where nothing of theirs touches. This one keeps the
|
|
284
|
+
* CONTOUR of each subtree instead -- its outline on each side, depth
|
|
285
|
+
* by depth -- and slides every sibling against the outline of the
|
|
286
|
+
* ones before it. A folded region then sits as close to an open one
|
|
287
|
+
* as their cards allow at each depth, which is what a folded treedb
|
|
288
|
+
* is: one branch open among closed ones.
|
|
289
|
+
*
|
|
290
|
+
* Same spanning tree, same order, same rows as the tidy tree: only
|
|
291
|
+
* the packing across the depths differs, so switching between the
|
|
292
|
+
* two moves cards sideways and never reorders them.
|
|
293
|
+
*
|
|
294
|
+
* The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
|
|
295
|
+
* Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
|
|
296
|
+
* -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
|
|
297
|
+
* maxGraph's CompactTreeLayout.ts: leaf, join, merge, offset and
|
|
298
|
+
* bridge below follow that form. What is ours: it runs on the
|
|
299
|
+
* spanning tree of a FOREST, every depth is a row as wide as its
|
|
300
|
+
* widest card (mxGraph's `alignRanks`), several roots sit side by
|
|
301
|
+
* side, and there is no recursion.
|
|
302
|
+
************************************************************/
|
|
303
|
+
export function layout_compact(nodes, edges, opts)
|
|
304
|
+
{
|
|
305
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
306
|
+
if(o.direction === "LR") {
|
|
307
|
+
return compact_tree_lr(nodes, edges, o);
|
|
308
|
+
}
|
|
309
|
+
let transposed = nodes.map((n) => Object.assign({}, n, {w: n.h, h: n.w}));
|
|
310
|
+
let lr = compact_tree_lr(transposed, edges, o);
|
|
311
|
+
let pos = new Map();
|
|
312
|
+
for(let [id, p] of lr) {
|
|
313
|
+
pos.set(id, {x: p.y, y: p.x});
|
|
314
|
+
}
|
|
315
|
+
return pos;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* One segment of a contour: a step of `dx` along the depth and `dy`
|
|
319
|
+
* across it, then the next segment. */
|
|
320
|
+
function contour_line(dx, dy, next)
|
|
321
|
+
{
|
|
322
|
+
return {dx: dx, dy: dy, next: next || null};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* How far the segment (a1, a2) at (p1, p2) has to move across to
|
|
326
|
+
* clear the segment (b1, b2) at the origin. */
|
|
327
|
+
function contour_offset(p1, p2, a1, a2, b1, b2)
|
|
328
|
+
{
|
|
329
|
+
if(b1 <= p1 || p1 + a1 <= 0) {
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
let d;
|
|
333
|
+
let t = b1 * a2 - a1 * b2;
|
|
334
|
+
if(t > 0) {
|
|
335
|
+
if(p1 < 0) {
|
|
336
|
+
d = (p1 * a2) / a1 - p2;
|
|
337
|
+
} else if(p1 > 0) {
|
|
338
|
+
d = (p1 * b2) / b1 - p2;
|
|
339
|
+
} else {
|
|
340
|
+
d = -p2;
|
|
341
|
+
}
|
|
342
|
+
} else if(b1 < p1 + a1) {
|
|
343
|
+
d = b2 - (p2 + ((b1 - p1) * a2) / a1);
|
|
344
|
+
} else if(b1 > p1 + a1) {
|
|
345
|
+
d = ((a1 + p1) * b2) / b1 - (p2 + a2);
|
|
346
|
+
} else {
|
|
347
|
+
d = b2 - (p2 + a2);
|
|
348
|
+
}
|
|
349
|
+
return (d > 0)? d : 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function contour_bridge(line1, x1, y1, line2, x2, y2)
|
|
353
|
+
{
|
|
354
|
+
let dx = x2 + line2.dx - x1;
|
|
355
|
+
let dy = (line2.dx === 0)? line2.dy : (dx * line2.dy) / line2.dx;
|
|
356
|
+
let r = contour_line(dx, dy, line2.next);
|
|
357
|
+
line1.next = contour_line(0, y2 + line2.dy - dy - y1, r);
|
|
358
|
+
return r;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Slide contour `p2` against `p1` until they clear; returns how far,
|
|
362
|
+
* and leaves in `p1` the outline of the two together. */
|
|
363
|
+
function contour_merge(p1, p2)
|
|
364
|
+
{
|
|
365
|
+
let x = 0;
|
|
366
|
+
let y = 0;
|
|
367
|
+
let total = 0;
|
|
368
|
+
let upper = p1.lower_head;
|
|
369
|
+
let lower = p2.upper_head;
|
|
370
|
+
|
|
371
|
+
while(lower && upper) {
|
|
372
|
+
let d = contour_offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
|
|
373
|
+
y += d;
|
|
374
|
+
total += d;
|
|
375
|
+
if(x + lower.dx <= upper.dx) {
|
|
376
|
+
x += lower.dx;
|
|
377
|
+
y += lower.dy;
|
|
378
|
+
lower = lower.next;
|
|
379
|
+
} else {
|
|
380
|
+
x -= upper.dx;
|
|
381
|
+
y -= upper.dy;
|
|
382
|
+
upper = upper.next;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if(lower) {
|
|
387
|
+
let b = contour_bridge(p1.upper_tail, 0, 0, lower, x, y);
|
|
388
|
+
p1.upper_tail = b.next? p2.upper_tail : b;
|
|
389
|
+
p1.lower_tail = p2.lower_tail;
|
|
390
|
+
} else {
|
|
391
|
+
let b = contour_bridge(p2.lower_tail, x, y, upper, 0, 0);
|
|
392
|
+
if(!b.next) {
|
|
393
|
+
p1.lower_tail = b;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
p1.lower_head = p2.lower_head;
|
|
397
|
+
return total;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function compact_tree_lr(nodes, edges, o)
|
|
401
|
+
{
|
|
402
|
+
let t = spanning_tree(nodes, edges);
|
|
403
|
+
let order = walk_order(t);
|
|
404
|
+
let pos = new Map();
|
|
405
|
+
|
|
406
|
+
/* Columns, as in the tidy tree: a depth is as wide as its widest
|
|
407
|
+
* card, and a column's gap is `ranksep`. */
|
|
408
|
+
let col_w = [];
|
|
409
|
+
for(let [id, d] of t.depth) {
|
|
410
|
+
col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
|
|
411
|
+
}
|
|
412
|
+
let col_x = [];
|
|
413
|
+
let x = 0;
|
|
414
|
+
for(let d = 0; d < col_w.length; d++) {
|
|
415
|
+
col_x[d] = x;
|
|
416
|
+
x += (col_w[d] || 0) + o.ranksep;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* mxGraph's nodeDistance is padding on EACH side of a card, so two
|
|
420
|
+
* neighbours end `nodesep` apart; its levelDistance is what is
|
|
421
|
+
* left of the column gap after that padding. */
|
|
422
|
+
let pad = o.nodesep / 2;
|
|
423
|
+
let dist = o.nodesep;
|
|
424
|
+
let level_distance = o.ranksep - pad;
|
|
425
|
+
|
|
426
|
+
/* `len` along the depth (the column's width: the rows are aligned),
|
|
427
|
+
* `ext` across it (the card's own height). */
|
|
428
|
+
let tn = new Map();
|
|
429
|
+
for(let id of order) {
|
|
430
|
+
tn.set(id, {
|
|
431
|
+
len: col_w[t.depth.get(id)],
|
|
432
|
+
ext: t.by_id.get(id).h,
|
|
433
|
+
off_x: 0,
|
|
434
|
+
off_y: 0,
|
|
435
|
+
y: 0,
|
|
436
|
+
contour: null,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* Contours, children before parents. */
|
|
441
|
+
for(let i = order.length - 1; i >= 0; i--) {
|
|
442
|
+
let id = order[i];
|
|
443
|
+
let n = tn.get(id);
|
|
444
|
+
let kids = t.children.get(id) || [];
|
|
445
|
+
|
|
446
|
+
if(!kids.length) {
|
|
447
|
+
let upper = contour_line(n.len + dist, 0);
|
|
448
|
+
let lower_tail = contour_line(0, -n.ext - dist);
|
|
449
|
+
n.contour = {
|
|
450
|
+
upper_head: upper,
|
|
451
|
+
upper_tail: upper,
|
|
452
|
+
lower_tail: lower_tail,
|
|
453
|
+
lower_head: contour_line(n.len + dist, 0, lower_tail),
|
|
454
|
+
};
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* join: the children side by side, each slid against the
|
|
459
|
+
* outline of the ones before it. */
|
|
460
|
+
let first = tn.get(kids[0]);
|
|
461
|
+
n.contour = first.contour;
|
|
462
|
+
let h = first.ext + dist;
|
|
463
|
+
let span = h;
|
|
464
|
+
for(let k = 1; k < kids.length; k++) {
|
|
465
|
+
let kid = tn.get(kids[k]);
|
|
466
|
+
let d = contour_merge(n.contour, kid.contour);
|
|
467
|
+
kid.off_y = d + h;
|
|
468
|
+
kid.off_x = 0;
|
|
469
|
+
h = kid.ext + dist;
|
|
470
|
+
span += d + h;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* attach: the parent centred across the span of its children,
|
|
474
|
+
* one column before them. */
|
|
475
|
+
let gap = pad + level_distance;
|
|
476
|
+
let y2 = (span - n.ext) / 2 - pad;
|
|
477
|
+
let y1 = y2 + n.ext + 2 * pad - span;
|
|
478
|
+
first.off_x = gap + n.len;
|
|
479
|
+
first.off_y = y1;
|
|
480
|
+
n.contour.upper_head = contour_line(n.len, 0,
|
|
481
|
+
contour_line(gap, y1, n.contour.upper_head));
|
|
482
|
+
n.contour.lower_head = contour_line(n.len, 0,
|
|
483
|
+
contour_line(gap, y2, n.contour.lower_head));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Positions across, parents before children: the first child at
|
|
487
|
+
* its offset from the parent, each next one at its offset from
|
|
488
|
+
* the one before. */
|
|
489
|
+
let root_of = new Map();
|
|
490
|
+
for(let id of order) {
|
|
491
|
+
let n = tn.get(id);
|
|
492
|
+
if(!root_of.has(id)) {
|
|
493
|
+
root_of.set(id, id);
|
|
494
|
+
}
|
|
495
|
+
let kids = t.children.get(id) || [];
|
|
496
|
+
if(!kids.length) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
let running = n.y + tn.get(kids[0]).off_y;
|
|
500
|
+
tn.get(kids[0]).y = running;
|
|
501
|
+
root_of.set(kids[0], root_of.get(id));
|
|
502
|
+
for(let k = 1; k < kids.length; k++) {
|
|
503
|
+
let kid = tn.get(kids[k]);
|
|
504
|
+
kid.y = running + kid.off_y;
|
|
505
|
+
running = kid.y;
|
|
506
|
+
root_of.set(kids[k], root_of.get(id));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* Several roots: each tree after the one before, by its extent. */
|
|
511
|
+
let lo = new Map();
|
|
512
|
+
let hi = new Map();
|
|
513
|
+
for(let id of order) {
|
|
514
|
+
let r = root_of.get(id);
|
|
515
|
+
let n = tn.get(id);
|
|
516
|
+
lo.set(r, Math.min(lo.has(r)? lo.get(r) : Infinity, n.y));
|
|
517
|
+
hi.set(r, Math.max(hi.has(r)? hi.get(r) : -Infinity, n.y + n.ext));
|
|
518
|
+
}
|
|
519
|
+
let shift = new Map();
|
|
520
|
+
let top = 0;
|
|
521
|
+
for(let r of t.roots) {
|
|
522
|
+
shift.set(r, top - lo.get(r));
|
|
523
|
+
top += hi.get(r) - lo.get(r) + o.nodesep * 2;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for(let id of order) {
|
|
527
|
+
let d = t.depth.get(id);
|
|
528
|
+
let n = tn.get(id);
|
|
529
|
+
let y = n.y + shift.get(root_of.get(id));
|
|
530
|
+
pos.set(id, {x: col_x[d] + col_w[d] / 2, y: y + n.ext / 2});
|
|
531
|
+
}
|
|
532
|
+
return pos;
|
|
533
|
+
}
|
|
534
|
+
|
|
277
535
|
/* How much of a card lies along the direction `a`: the projection
|
|
278
536
|
* of its rectangle on that direction. Full width when the card is
|
|
279
537
|
* read along its width, full height across it, and the honest
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* tidy tree's rows and centring, the radial tree's rings.
|
|
7
7
|
***********************************************************************/
|
|
8
8
|
import { describe, test, expect } from "vitest";
|
|
9
|
-
import { spanning_tree, layout_tree, layout_radial } from "./treedb_layout.js";
|
|
9
|
+
import { spanning_tree, layout_tree, layout_radial, layout_compact } from "./treedb_layout.js";
|
|
10
10
|
|
|
11
11
|
/* es -> (norte, sur); norte -> nave; nave -> d0, d1 (hook rank 1) and
|
|
12
12
|
* c1 (hook rank 2); c1 -> d0 as well (second parent). */
|
|
@@ -97,6 +97,107 @@ describe("the tidy tree, read down (the default)", () => {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
/* Every two cards of one row stand at least `gap` apart. */
|
|
101
|
+
function expect_no_overlap_in_rows(list, pos, gap)
|
|
102
|
+
{
|
|
103
|
+
for(let i = 0; i < list.length; i++) {
|
|
104
|
+
for(let j = i + 1; j < list.length; j++) {
|
|
105
|
+
let a = list[i];
|
|
106
|
+
let b = list[j];
|
|
107
|
+
let pa = pos.get(a.id);
|
|
108
|
+
let pb = pos.get(b.id);
|
|
109
|
+
if(Math.abs(pa.y - pb.y) > 1e-6) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
expect(Math.abs(pa.x - pb.x) + 1e-6).toBeGreaterThanOrEqual((a.w + b.w) / 2 + gap);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function width_of(list, pos)
|
|
118
|
+
{
|
|
119
|
+
let lo = Infinity;
|
|
120
|
+
let hi = -Infinity;
|
|
121
|
+
for(let n of list) {
|
|
122
|
+
let p = pos.get(n.id);
|
|
123
|
+
lo = Math.min(lo, p.x - n.w / 2);
|
|
124
|
+
hi = Math.max(hi, p.x + n.w / 2);
|
|
125
|
+
}
|
|
126
|
+
return hi - lo;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* r -> a, b; a -> hall -> six devices; b is a closed sibling. */
|
|
130
|
+
const wide = [N("r", 172, 96), N("a", 172, 96), N("b", 172, 96), N("hall", 172, 96)]
|
|
131
|
+
.concat([0, 1, 2, 3, 4, 5].map((i) => N(`d${i}`, 116, 40)));
|
|
132
|
+
const wide_edges = [
|
|
133
|
+
{source: "r", target: "a"}, {source: "r", target: "b"}, {source: "a", target: "hall"},
|
|
134
|
+
].concat([0, 1, 2, 3, 4, 5].map((i) => ({source: "hall", target: `d${i}`})));
|
|
135
|
+
|
|
136
|
+
describe("the compact tree", () => {
|
|
137
|
+
test("the rows are the tidy tree's rows", () => {
|
|
138
|
+
let tidy = layout_tree(nodes, edges, {nodesep: 10, ranksep: 100});
|
|
139
|
+
let pos = layout_compact(nodes, edges, {nodesep: 10, ranksep: 100});
|
|
140
|
+
for(let n of nodes) {
|
|
141
|
+
expect(pos.get(n.id).y).toBe(tidy.get(n.id).y);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("no two cards of a row are closer than the gap", () => {
|
|
146
|
+
expect_no_overlap_in_rows(nodes, layout_compact(nodes, edges, {nodesep: 10, ranksep: 100}), 10);
|
|
147
|
+
expect_no_overlap_in_rows(wide, layout_compact(wide, wide_edges, {nodesep: 18, ranksep: 90}), 18);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("a parent is centred over its children, which keep their order", () => {
|
|
151
|
+
let pos = layout_compact(nodes, edges, {nodesep: 10, ranksep: 100});
|
|
152
|
+
expect(pos.get("d0").x).toBeLessThan(pos.get("d1").x);
|
|
153
|
+
expect(pos.get("d1").x).toBeLessThan(pos.get("c1").x);
|
|
154
|
+
let first = pos.get("d0").x - 58;
|
|
155
|
+
let last = pos.get("c1").x + 86;
|
|
156
|
+
expect(pos.get("nave").x).toBeCloseTo((first + last) / 2, 6);
|
|
157
|
+
expect(pos.get("norte").x).toBeLessThan(pos.get("sur").x);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a closed sibling sits beside an open branch, not beside its block", () => {
|
|
161
|
+
let opts = {nodesep: 18, ranksep: 90};
|
|
162
|
+
let tidy = layout_tree(wide, wide_edges, opts);
|
|
163
|
+
let pos = layout_compact(wide, wide_edges, opts);
|
|
164
|
+
/* In the tidy tree `b` waits for the whole fan of devices; here it
|
|
165
|
+
* only has to clear `a`, the card beside it. */
|
|
166
|
+
expect(pos.get("b").x - pos.get("a").x).toBeLessThan(tidy.get("b").x - tidy.get("a").x);
|
|
167
|
+
expect(pos.get("b").x - pos.get("a").x).toBeGreaterThanOrEqual(172 + 18 - 1e-6);
|
|
168
|
+
expect(width_of(wide, pos)).toBeLessThanOrEqual(width_of(wide, tidy));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("two trees sit side by side, not overlapped", () => {
|
|
172
|
+
let pos = layout_compact([N("a", 100, 50), N("b", 100, 50)], [], {nodesep: 10});
|
|
173
|
+
expect(pos.get("b").x - pos.get("a").x).toBe(100 + 20);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("read right, it is the same tree with the axes swapped", () => {
|
|
177
|
+
let down = layout_compact(wide, wide_edges, {nodesep: 18, ranksep: 90});
|
|
178
|
+
let swapped = wide.map((n) => N(n.id, n.h, n.w));
|
|
179
|
+
let right = layout_compact(swapped, wide_edges, {nodesep: 18, ranksep: 90, direction: "LR"});
|
|
180
|
+
for(let n of wide) {
|
|
181
|
+
expect(right.get(n.id).x).toBeCloseTo(down.get(n.id).y, 6);
|
|
182
|
+
expect(right.get(n.id).y).toBeCloseTo(down.get(n.id).x, 6);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("a tree as deep as the data places every node, without recursion", () => {
|
|
187
|
+
let chain = [];
|
|
188
|
+
let links = [];
|
|
189
|
+
for(let i = 0; i < 5000; i++) {
|
|
190
|
+
chain.push(N(`n${i}`, 40, 20));
|
|
191
|
+
if(i > 0) {
|
|
192
|
+
links.push({source: `n${i - 1}`, target: `n${i}`});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
let pos = layout_compact(chain, links, {});
|
|
196
|
+
expect(pos.size).toBe(5000);
|
|
197
|
+
expect(Number.isFinite(pos.get("n4999").x)).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
100
201
|
describe("the tidy tree, read right", () => {
|
|
101
202
|
test("depths are columns; a column is as wide as its widest card", () => {
|
|
102
203
|
let pos = layout_tree(nodes, edges, {nodesep: 10, ranksep: 100, direction: "LR"});
|