@yuneta/gobj-ui 7.23.146 → 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 +23 -6
- package/dist/gobj-ui.cjs.js +221 -10
- package/dist/gobj-ui.es.js +221 -10
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +20 -1
- package/src/c_yui_json.css +0 -7
- package/src/c_yui_json.js +2 -2
- package/src/c_yui_schema_editor.js +1 -1
- package/src/c_yui_treedb_graph.js +8 -5
- package/src/treedb_layout.js +258 -0
- package/src/treedb_layout.test.js +102 -1
- package/src/yui_graph_camera.js +6 -7
- package/src/yui_icons.css +40 -0
package/README.md
CHANGED
|
@@ -497,8 +497,10 @@ cloned toolbar cannot drift apart again. The vocabulary is the treedb graph's
|
|
|
497
497
|
sprite and so cannot share the code, only the decisions): zoom in / zoom out, a
|
|
498
498
|
live **zoom readout**, `yi-fit` for fit — the same corner brackets the sprite
|
|
499
499
|
draws for `g6-icon-fit` — and the WRITTEN `1:1` for actual size, which is
|
|
500
|
-
written in every editor that offers it and never drawn. Fold is the
|
|
501
|
-
pair
|
|
500
|
+
written in every editor that offers it and never drawn. Fold is the
|
|
501
|
+
double-chevron pair (`yi-angles-down` / `yi-angles-up`, `7.23.147`), the same
|
|
502
|
+
as the lazy tree viewer's toolbar: not the single chevron of a node's handle,
|
|
503
|
+
which opens ONE node. The readout hangs off G6's
|
|
502
504
|
`aftertransform`, the one hook that also covers the wheel.
|
|
503
505
|
|
|
504
506
|
Both graph toolbars are arranged the same way (`7.23.7`): the **global**
|
|
@@ -1385,8 +1387,8 @@ G6 5.1 registers these layouts, and this is how each meets that shape:
|
|
|
1385
1387
|
| `circular`, `concentric`, `grid`, `mds`, `random` | rings, rings by degree, a grid, projection, noise | none knows a parent from a child |
|
|
1386
1388
|
| `combo-combined`, `fishbone` | combos / cause-effect | not this data |
|
|
1387
1389
|
|
|
1388
|
-
So
|
|
1389
|
-
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`:
|
|
1390
1392
|
|
|
1391
1393
|
- **`treedb-tree`** — the classic tidy tree, **top to bottom**. A row per
|
|
1392
1394
|
depth as tall as its tallest card, a node centred over the block of its
|
|
@@ -1400,6 +1402,18 @@ layouts by two thin adapters in `c_g6_nodes_tree.js`:
|
|
|
1400
1402
|
keeps the other reading for a host that wants it). The outline that shipped
|
|
1401
1403
|
beside it (`treedb-outline`, one row per node) was removed in `7.23.75`: a
|
|
1402
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.
|
|
1403
1417
|
- **`radial`** (`treedb-radial`) — the root in the middle, a ring per depth,
|
|
1404
1418
|
every subtree an angular **sector** proportional to its leaves, and the
|
|
1405
1419
|
radius of each ring the largest of three: one `ranksep` out from the ring
|
|
@@ -1411,7 +1425,7 @@ layouts by two thin adapters in `c_g6_nodes_tree.js`:
|
|
|
1411
1425
|
construction, across the ring or along the radius; several roots share the
|
|
1412
1426
|
circle around an empty centre.
|
|
1413
1427
|
|
|
1414
|
-
|
|
1428
|
+
All three take the **same spanning tree**, chosen deterministically: roots are the
|
|
1415
1429
|
nodes with no incoming edge, in node order; a node belongs to the **first
|
|
1416
1430
|
parent that reaches it** in a breadth-first walk (the place, not the
|
|
1417
1431
|
controller, because the place's column came first) and its other links are
|
|
@@ -1664,7 +1678,10 @@ forwards them as `EV_SET_HIDDEN_TOPICS`, `EV_SET_MAIN_TOPIC`,
|
|
|
1664
1678
|
`main topic`, `highlight topic`, `loose records`, `hidden topics`; `legend`
|
|
1665
1679
|
is no longer used.
|
|
1666
1680
|
|
|
1667
|
-
**The layout select has a stepper beside it** (`7.23.146`):
|
|
1681
|
+
**The layout select has a stepper beside it** (`7.23.146`): ↑ and ↓
|
|
1682
|
+
(`yi-arrow-up` / `yi-arrow-down` since `7.23.147`, a shafted arrow steps a
|
|
1683
|
+
sequence; the fold stepper's level buttons are tree boxes, `yi-square-minus`
|
|
1684
|
+
/ `yi-square-plus`) step to
|
|
1668
1685
|
the layout before or after the current one, in the order the engine offers
|
|
1669
1686
|
them (`layout_names`), without opening the list. Layouts are picked by trying
|
|
1670
1687
|
them one after another, and a list closes on every pick. The stepper reads the
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -11305,13 +11305,13 @@ function yui_graph_camera_items(gobj, graph, wide) {
|
|
|
11305
11305
|
];
|
|
11306
11306
|
}
|
|
11307
11307
|
/************************************************************
|
|
11308
|
-
* The fold pair: expand all, collapse all.
|
|
11309
|
-
*
|
|
11310
|
-
*
|
|
11311
|
-
*
|
|
11308
|
+
* The fold pair: expand all, collapse all. Double chevrons,
|
|
11309
|
+
* down and up -- the same pair the lazy tree viewer's toolbar
|
|
11310
|
+
* uses. Not the single chevron of a node's handle: that opens
|
|
11311
|
+
* ONE node, these act on all of them.
|
|
11312
11312
|
************************************************************/
|
|
11313
11313
|
function yui_graph_fold_items(gobj, wide) {
|
|
11314
|
-
return [camera_button(gobj, "yi-
|
|
11314
|
+
return [camera_button(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand all", wide), camera_button(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all", wide)];
|
|
11315
11315
|
}
|
|
11316
11316
|
/************************************************************
|
|
11317
11317
|
* The ANCHOR toggle: pick one element and every camera move
|
|
@@ -13851,7 +13851,7 @@ function make_toolbar$4(gobj) {
|
|
|
13851
13851
|
[yui_toolbar_icon("yi-magnifying-glass")]
|
|
13852
13852
|
]]
|
|
13853
13853
|
]);
|
|
13854
|
-
left_items.push(icon_button$1(gobj, "yi-
|
|
13854
|
+
left_items.push(icon_button$1(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand loaded"), icon_button$1(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all"));
|
|
13855
13855
|
attach_clear($search_control, $search_input);
|
|
13856
13856
|
left_items.push($search_control);
|
|
13857
13857
|
let right_items = [view_mode_switch$1(gobj), icon_button$1(gobj, "yi-copy", "EV_COPY_ALL", "copy json")];
|
|
@@ -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");
|
|
@@ -64418,7 +64629,7 @@ function fold_level_items(gobj, wide) {
|
|
|
64418
64629
|
style: "margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
|
|
64419
64630
|
},
|
|
64420
64631
|
[
|
|
64421
|
-
step_button("GRAPH_FOLD_COLLAPSE", "yi-
|
|
64632
|
+
step_button("GRAPH_FOLD_COLLAPSE", "yi-square-minus", "collapse one level", "EV_COLLAPSE_LEVEL"),
|
|
64422
64633
|
[
|
|
64423
64634
|
"div",
|
|
64424
64635
|
{
|
|
@@ -64459,7 +64670,7 @@ function fold_level_items(gobj, wide) {
|
|
|
64459
64670
|
]
|
|
64460
64671
|
]
|
|
64461
64672
|
],
|
|
64462
|
-
step_button("GRAPH_FOLD_EXPAND", "yi-
|
|
64673
|
+
step_button("GRAPH_FOLD_EXPAND", "yi-square-plus", "expand one level", "EV_EXPAND_LEVEL")
|
|
64463
64674
|
]
|
|
64464
64675
|
]];
|
|
64465
64676
|
}
|
|
@@ -64562,7 +64773,7 @@ function make_toolbar(gobj) {
|
|
|
64562
64773
|
class: "GRAPH_LAYOUT_STEPPER buttons has-addons",
|
|
64563
64774
|
style: "margin:0 0 0 .25rem; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
|
|
64564
64775
|
},
|
|
64565
|
-
[layout_step_button(gobj, "GRAPH_LAYOUT_PREV", "yi-
|
|
64776
|
+
[layout_step_button(gobj, "GRAPH_LAYOUT_PREV", "yi-arrow-up", "previous layout", -1), layout_step_button(gobj, "GRAPH_LAYOUT_NEXT", "yi-arrow-down", "next layout", 1)]
|
|
64566
64777
|
],
|
|
64567
64778
|
[
|
|
64568
64779
|
"span",
|
|
@@ -69869,7 +70080,7 @@ function version_banner(gobj, topic) {
|
|
|
69869
70080
|
[[
|
|
69870
70081
|
"span",
|
|
69871
70082
|
{ class: "icon" },
|
|
69872
|
-
[["i", { class: "yi-
|
|
70083
|
+
[["i", { class: "yi-circle-up" }]]
|
|
69873
70084
|
], [
|
|
69874
70085
|
"span",
|
|
69875
70086
|
{ i18n: "raise" },
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -11300,13 +11300,13 @@ function yui_graph_camera_items(gobj, graph, wide) {
|
|
|
11300
11300
|
];
|
|
11301
11301
|
}
|
|
11302
11302
|
/************************************************************
|
|
11303
|
-
* The fold pair: expand all, collapse all.
|
|
11304
|
-
*
|
|
11305
|
-
*
|
|
11306
|
-
*
|
|
11303
|
+
* The fold pair: expand all, collapse all. Double chevrons,
|
|
11304
|
+
* down and up -- the same pair the lazy tree viewer's toolbar
|
|
11305
|
+
* uses. Not the single chevron of a node's handle: that opens
|
|
11306
|
+
* ONE node, these act on all of them.
|
|
11307
11307
|
************************************************************/
|
|
11308
11308
|
function yui_graph_fold_items(gobj, wide) {
|
|
11309
|
-
return [camera_button(gobj, "yi-
|
|
11309
|
+
return [camera_button(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand all", wide), camera_button(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all", wide)];
|
|
11310
11310
|
}
|
|
11311
11311
|
/************************************************************
|
|
11312
11312
|
* The ANCHOR toggle: pick one element and every camera move
|
|
@@ -13846,7 +13846,7 @@ function make_toolbar$4(gobj) {
|
|
|
13846
13846
|
[yui_toolbar_icon("yi-magnifying-glass")]
|
|
13847
13847
|
]]
|
|
13848
13848
|
]);
|
|
13849
|
-
left_items.push(icon_button$1(gobj, "yi-
|
|
13849
|
+
left_items.push(icon_button$1(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand loaded"), icon_button$1(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all"));
|
|
13850
13850
|
attach_clear($search_control, $search_input);
|
|
13851
13851
|
left_items.push($search_control);
|
|
13852
13852
|
let right_items = [view_mode_switch$1(gobj), icon_button$1(gobj, "yi-copy", "EV_COPY_ALL", "copy json")];
|
|
@@ -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");
|
|
@@ -64413,7 +64624,7 @@ function fold_level_items(gobj, wide) {
|
|
|
64413
64624
|
style: "margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
|
|
64414
64625
|
},
|
|
64415
64626
|
[
|
|
64416
|
-
step_button("GRAPH_FOLD_COLLAPSE", "yi-
|
|
64627
|
+
step_button("GRAPH_FOLD_COLLAPSE", "yi-square-minus", "collapse one level", "EV_COLLAPSE_LEVEL"),
|
|
64417
64628
|
[
|
|
64418
64629
|
"div",
|
|
64419
64630
|
{
|
|
@@ -64454,7 +64665,7 @@ function fold_level_items(gobj, wide) {
|
|
|
64454
64665
|
]
|
|
64455
64666
|
]
|
|
64456
64667
|
],
|
|
64457
|
-
step_button("GRAPH_FOLD_EXPAND", "yi-
|
|
64668
|
+
step_button("GRAPH_FOLD_EXPAND", "yi-square-plus", "expand one level", "EV_EXPAND_LEVEL")
|
|
64458
64669
|
]
|
|
64459
64670
|
]];
|
|
64460
64671
|
}
|
|
@@ -64557,7 +64768,7 @@ function make_toolbar(gobj) {
|
|
|
64557
64768
|
class: "GRAPH_LAYOUT_STEPPER buttons has-addons",
|
|
64558
64769
|
style: "margin:0 0 0 .25rem; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;"
|
|
64559
64770
|
},
|
|
64560
|
-
[layout_step_button(gobj, "GRAPH_LAYOUT_PREV", "yi-
|
|
64771
|
+
[layout_step_button(gobj, "GRAPH_LAYOUT_PREV", "yi-arrow-up", "previous layout", -1), layout_step_button(gobj, "GRAPH_LAYOUT_NEXT", "yi-arrow-down", "next layout", 1)]
|
|
64561
64772
|
],
|
|
64562
64773
|
[
|
|
64563
64774
|
"span",
|
|
@@ -69864,7 +70075,7 @@ function version_banner(gobj, topic) {
|
|
|
69864
70075
|
[[
|
|
69865
70076
|
"span",
|
|
69866
70077
|
{ class: "icon" },
|
|
69867
|
-
[["i", { class: "yi-
|
|
70078
|
+
[["i", { class: "yi-circle-up" }]]
|
|
69868
70079
|
], [
|
|
69869
70080
|
"span",
|
|
69870
70081
|
{ i18n: "raise" },
|
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/c_yui_json.css
CHANGED
|
@@ -155,13 +155,6 @@
|
|
|
155
155
|
transform: rotate(90deg);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/* "Expand all" reuses the chevron (the only one available) rotated to point
|
|
159
|
-
* DOWN — the open state — while "collapse all" keeps it pointing right (the
|
|
160
|
-
* closed state), mirroring the per-node toggle. */
|
|
161
|
-
.C_YUI_JSON .EV_EXPAND_ALL i {
|
|
162
|
-
transform: rotate(90deg);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
158
|
.C_YUI_JSON .JSON_TOGGLE_REMOTE i {
|
|
166
159
|
font-size: 0.8em;
|
|
167
160
|
color: var(--bulma-link-on-scheme, var(--bulma-link, #3273dc));
|
package/src/c_yui_json.js
CHANGED
|
@@ -399,8 +399,8 @@ function make_toolbar(gobj)
|
|
|
399
399
|
* place both graphs put theirs. It opens the whole document; the
|
|
400
400
|
* per-row toggles live in the rows. */
|
|
401
401
|
left_items.push(
|
|
402
|
-
icon_button(gobj, "yi-
|
|
403
|
-
icon_button(gobj, "yi-
|
|
402
|
+
icon_button(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand loaded"),
|
|
403
|
+
icon_button(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all")
|
|
404
404
|
);
|
|
405
405
|
|
|
406
406
|
attach_clear($search_control, $search_input);
|
|
@@ -1264,7 +1264,7 @@ function version_banner(gobj, topic)
|
|
|
1264
1264
|
title: t("raise the version"), "aria-label": t("raise the version"),
|
|
1265
1265
|
"data-i18n-title": "raise the version",
|
|
1266
1266
|
"data-i18n-aria-label": "raise the version"}, [
|
|
1267
|
-
["span", {class: "icon"}, [["i", {class: "yi-
|
|
1267
|
+
["span", {class: "icon"}, [["i", {class: "yi-circle-up"}]]],
|
|
1268
1268
|
["span", {i18n: "raise"}, t("raise")]
|
|
1269
1269
|
]]
|
|
1270
1270
|
]]
|
|
@@ -553,6 +553,8 @@ function option_label(name)
|
|
|
553
553
|
return t("manual");
|
|
554
554
|
case "treedb-tree":
|
|
555
555
|
return t("treedb-tree");
|
|
556
|
+
case "compact-tree":
|
|
557
|
+
return t("compact-tree");
|
|
556
558
|
case "radial":
|
|
557
559
|
return t("radial");
|
|
558
560
|
case "dagre":
|
|
@@ -716,8 +718,9 @@ function fold_level_items(gobj, wide)
|
|
|
716
718
|
return [
|
|
717
719
|
['div', {class: 'GRAPH_FOLD_STEPPER buttons has-addons',
|
|
718
720
|
style: 'margin:0; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;'}, [
|
|
719
|
-
/*
|
|
720
|
-
|
|
721
|
+
/* Tree boxes, not the pills' chevrons: a pill opens ONE hook,
|
|
722
|
+
* this steps a LEVEL of the whole tree. */
|
|
723
|
+
step_button('GRAPH_FOLD_COLLAPSE', 'yi-square-minus',
|
|
721
724
|
'collapse one level', 'EV_COLLAPSE_LEVEL'),
|
|
722
725
|
/* A `div` with the button skin, like the legend's main
|
|
723
726
|
* chip: a readout between two buttons of one group, not
|
|
@@ -740,7 +743,7 @@ function fold_level_items(gobj, wide)
|
|
|
740
743
|
['span', {class: 'GRAPH_FOLD_COUNT',
|
|
741
744
|
style: 'font-variant-numeric:tabular-nums;'}, '–'],
|
|
742
745
|
]],
|
|
743
|
-
step_button('GRAPH_FOLD_EXPAND', 'yi-
|
|
746
|
+
step_button('GRAPH_FOLD_EXPAND', 'yi-square-plus',
|
|
744
747
|
'expand one level', 'EV_EXPAND_LEVEL'),
|
|
745
748
|
]],
|
|
746
749
|
];
|
|
@@ -881,8 +884,8 @@ function make_toolbar(gobj)
|
|
|
881
884
|
* arrive with the child (populate_nodes_tree_options). */
|
|
882
885
|
['div', {class: 'GRAPH_LAYOUT_STEPPER buttons has-addons',
|
|
883
886
|
style: 'margin:0 0 0 .25rem; flex:0 0 auto; flex-wrap:nowrap; align-items:stretch;'}, [
|
|
884
|
-
layout_step_button(gobj, 'GRAPH_LAYOUT_PREV', 'yi-
|
|
885
|
-
layout_step_button(gobj, 'GRAPH_LAYOUT_NEXT', 'yi-
|
|
887
|
+
layout_step_button(gobj, 'GRAPH_LAYOUT_PREV', 'yi-arrow-up', 'previous layout', -1),
|
|
888
|
+
layout_step_button(gobj, 'GRAPH_LAYOUT_NEXT', 'yi-arrow-down', 'next layout', 1),
|
|
886
889
|
]],
|
|
887
890
|
|
|
888
891
|
['span', {class: 'GRAPH_MODE_LABEL is-hidden-mobile', style: 'padding-left:10px; padding-right:5px;', i18n: 'operation mode'}, 'operation mode'],
|
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"});
|
package/src/yui_graph_camera.js
CHANGED
|
@@ -181,17 +181,16 @@ export function yui_graph_camera_items(gobj, graph, wide)
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/************************************************************
|
|
184
|
-
* The fold pair: expand all, collapse all.
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
184
|
+
* The fold pair: expand all, collapse all. Double chevrons,
|
|
185
|
+
* down and up -- the same pair the lazy tree viewer's toolbar
|
|
186
|
+
* uses. Not the single chevron of a node's handle: that opens
|
|
187
|
+
* ONE node, these act on all of them.
|
|
188
188
|
************************************************************/
|
|
189
189
|
export function yui_graph_fold_items(gobj, wide)
|
|
190
190
|
{
|
|
191
191
|
return [
|
|
192
|
-
camera_button(gobj, "yi-
|
|
193
|
-
|
|
194
|
-
camera_button(gobj, "yi-chevron-right", "EV_COLLAPSE_ALL", "collapse all", wide),
|
|
192
|
+
camera_button(gobj, "yi-angles-down", "EV_EXPAND_ALL", "expand all", wide),
|
|
193
|
+
camera_button(gobj, "yi-angles-up", "EV_COLLAPSE_ALL", "collapse all", wide),
|
|
195
194
|
];
|
|
196
195
|
}
|
|
197
196
|
|
package/src/yui_icons.css
CHANGED
|
@@ -404,3 +404,43 @@
|
|
|
404
404
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M352 64L480 160 352 256V192H64V128H352ZM160 256L32 352 160 448V384H448V320H160Z'/%3E%3C/svg%3E");
|
|
405
405
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M352 64L480 160 352 256V192H64V128H352ZM160 256L32 352 160 448V384H448V320H160Z'/%3E%3C/svg%3E");
|
|
406
406
|
}
|
|
407
|
+
|
|
408
|
+
/* --- One shape per meaning (FontAwesome Free 7.3.1, solid) ---
|
|
409
|
+
* A chevron opens or closes ONE thing, or scrolls; a shafted arrow
|
|
410
|
+
* steps through a sequence; a tree box steps one LEVEL of a tree;
|
|
411
|
+
* a double chevron acts on ALL of it. */
|
|
412
|
+
|
|
413
|
+
.yi-square-plus::before {
|
|
414
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM200 344l0-64-64 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l64 0 0-64c0-13.3 10.7-24 24-24s24 10.7 24 24l0 64 64 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-64 0 0 64c0 13.3-10.7 24-24 24s-24-10.7-24-24z'/%3E%3C/svg%3E");
|
|
415
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM200 344l0-64-64 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l64 0 0-64c0-13.3 10.7-24 24-24s24 10.7 24 24l0 64 64 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-64 0 0 64c0 13.3-10.7 24-24 24s-24-10.7-24-24z'/%3E%3C/svg%3E");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.yi-square-minus::before {
|
|
419
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm72 200l176 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-176 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z'/%3E%3C/svg%3E");
|
|
420
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm72 200l176 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-176 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z'/%3E%3C/svg%3E");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.yi-angles-down::before {
|
|
424
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 470.6c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 402.7 329.4 265.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-160 160zm160-352l-160 160c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 210.7 329.4 73.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3z'/%3E%3C/svg%3E");
|
|
425
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 470.6c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 402.7 329.4 265.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3l-160 160zm160-352l-160 160c-12.5 12.5-32.8 12.5-45.3 0l-160-160c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 210.7 329.4 73.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3z'/%3E%3C/svg%3E");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.yi-angles-up::before {
|
|
429
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 109.3 329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160zm160 352l-160-160c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 329.4 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3z'/%3E%3C/svg%3E");
|
|
430
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 109.3 329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160zm160 352l-160-160c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 329.4 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3z'/%3E%3C/svg%3E");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.yi-arrow-up::before {
|
|
434
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 109.3 160 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z'/%3E%3C/svg%3E");
|
|
435
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 109.3 160 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z'/%3E%3C/svg%3E");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.yi-arrow-down::before {
|
|
439
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z'/%3E%3C/svg%3E");
|
|
440
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z'/%3E%3C/svg%3E");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.yi-circle-up::before {
|
|
444
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
|
|
445
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
|
|
446
|
+
}
|