@yuneta/gobj-ui 7.23.69 → 7.23.70
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 +49 -0
- package/dist/gobj-ui.cjs.js +418 -2
- package/dist/gobj-ui.es.js +418 -2
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +103 -2
- package/src/c_yui_treedb_graph.js +6 -0
- package/src/treedb_layout.js +380 -0
- package/src/treedb_layout.test.js +172 -0
package/dist/gobj-ui.es.js
CHANGED
|
@@ -27309,6 +27309,329 @@ function fold_reveal(model, state, node_key) {
|
|
|
27309
27309
|
return true;
|
|
27310
27310
|
}
|
|
27311
27311
|
//#endregion
|
|
27312
|
+
//#region src/treedb_layout.js
|
|
27313
|
+
/***********************************************************************
|
|
27314
|
+
* treedb_layout.js
|
|
27315
|
+
*
|
|
27316
|
+
* Two layouts made for a treedb, and nothing else.
|
|
27317
|
+
*
|
|
27318
|
+
* What a treedb graph IS, once it is folded (treedb_fold_model.js):
|
|
27319
|
+
* a forest read left to right. A main topic is a tree (places:
|
|
27320
|
+
* country, region, site, hall), the other topics hang from its
|
|
27321
|
+
* nodes through hooks (devices, users, controllers), a record may
|
|
27322
|
+
* hang from two parents (a device from its place AND its
|
|
27323
|
+
* controller), and every visible node was reached from a root.
|
|
27324
|
+
* Tens to a few hundred cards on screen, never the whole store.
|
|
27325
|
+
*
|
|
27326
|
+
* G6's own layouts either do not know that or pay for not knowing:
|
|
27327
|
+
*
|
|
27328
|
+
* - `dagre` gets the reading direction right and then ranks by
|
|
27329
|
+
* longest path and minimises crossings with a heuristic that
|
|
27330
|
+
* REORDERS the siblings on every run -- open one hook and the
|
|
27331
|
+
* column next to it shuffles. It is O(n·m) per sweep and it is
|
|
27332
|
+
* the one thing that takes time on a big expansion.
|
|
27333
|
+
* - the tree layouts (`compact-box`, `indented`, `dendrogram`,
|
|
27334
|
+
* `mindmap`) draw exactly the shape wanted, but G6 runs them on
|
|
27335
|
+
* a TREE it builds from the edges: a node with two parents is
|
|
27336
|
+
* placed by whichever parent the builder met first, unstably.
|
|
27337
|
+
* - the force family scatters a tree, which is the pile with
|
|
27338
|
+
* physics.
|
|
27339
|
+
*
|
|
27340
|
+
* So: a tidy tree (`tree`) and an outline (`outline`), both fed by
|
|
27341
|
+
* the SAME spanning tree, chosen deterministically:
|
|
27342
|
+
*
|
|
27343
|
+
* - roots are the nodes with no incoming edge, in node order;
|
|
27344
|
+
* - a node belongs to the FIRST parent that reaches it in a
|
|
27345
|
+
* breadth-first walk from the roots -- the place, not the
|
|
27346
|
+
* controller, because the place's column came first -- and the
|
|
27347
|
+
* other links are drawn as edges across the tree;
|
|
27348
|
+
* - the children of a node are ordered by the HOOK they hang from
|
|
27349
|
+
* (the parent's port order, which is the schema's column order)
|
|
27350
|
+
* and then by node order (record order, the pages' order);
|
|
27351
|
+
* - a node nothing reaches (a cycle) is a root too.
|
|
27352
|
+
*
|
|
27353
|
+
* `tree`: the classic tidy tree, left to right. Each depth is a
|
|
27354
|
+
* column as wide as its widest card; a node is centred on the
|
|
27355
|
+
* block of its children; siblings stack with `nodesep` between
|
|
27356
|
+
* them. O(n), and opening a hook moves NOTHING that is not below
|
|
27357
|
+
* or beside it -- the cards keep their order, the eye keeps its
|
|
27358
|
+
* place.
|
|
27359
|
+
*
|
|
27360
|
+
* `outline`: one node per row, indented by depth -- what a JSON
|
|
27361
|
+
* viewer draws, and what a treedb is when read as one. Tall, but
|
|
27362
|
+
* nothing is ever beside anything: the reader scrolls a list.
|
|
27363
|
+
*
|
|
27364
|
+
* Pure: takes plain nodes and edges, returns positions. The G6
|
|
27365
|
+
* classes in c_g6_nodes_tree.js are two thin adapters over it.
|
|
27366
|
+
*
|
|
27367
|
+
* Copyright (c) 2026, ArtGins.
|
|
27368
|
+
* All Rights Reserved.
|
|
27369
|
+
***********************************************************************/
|
|
27370
|
+
var DEFAULTS = {
|
|
27371
|
+
nodesep: 18,
|
|
27372
|
+
ranksep: 110,
|
|
27373
|
+
indent: 48
|
|
27374
|
+
};
|
|
27375
|
+
/************************************************************
|
|
27376
|
+
* The spanning tree of a graph, deterministic.
|
|
27377
|
+
*
|
|
27378
|
+
* nodes [{id, w, h, order}] order: creation index
|
|
27379
|
+
* edges [{source, target, rank}] rank: the hook's position in
|
|
27380
|
+
* the parent's ports (0 = first)
|
|
27381
|
+
*
|
|
27382
|
+
* Returns {roots: [id], children: Map<id, [id]>, depth: Map<id, n>,
|
|
27383
|
+
* by_id: Map<id, node>}
|
|
27384
|
+
************************************************************/
|
|
27385
|
+
function spanning_tree(nodes, edges) {
|
|
27386
|
+
let by_id = /* @__PURE__ */ new Map();
|
|
27387
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
27388
|
+
let n = nodes[i];
|
|
27389
|
+
by_id.set(n.id, Object.assign({ order: i }, n));
|
|
27390
|
+
}
|
|
27391
|
+
let out = /* @__PURE__ */ new Map();
|
|
27392
|
+
let has_parent = /* @__PURE__ */ new Set();
|
|
27393
|
+
for (let e of edges) {
|
|
27394
|
+
if (!by_id.has(e.source) || !by_id.has(e.target) || e.source === e.target) continue;
|
|
27395
|
+
if (!out.has(e.source)) out.set(e.source, []);
|
|
27396
|
+
out.get(e.source).push({
|
|
27397
|
+
id: e.target,
|
|
27398
|
+
rank: e.rank > 0 ? e.rank : 0
|
|
27399
|
+
});
|
|
27400
|
+
has_parent.add(e.target);
|
|
27401
|
+
}
|
|
27402
|
+
for (let list of out.values()) list.sort((a, b) => {
|
|
27403
|
+
if (a.rank !== b.rank) return a.rank - b.rank;
|
|
27404
|
+
return by_id.get(a.id).order - by_id.get(b.id).order;
|
|
27405
|
+
});
|
|
27406
|
+
let roots = [];
|
|
27407
|
+
for (let n of by_id.values()) if (!has_parent.has(n.id)) roots.push(n.id);
|
|
27408
|
+
let children = /* @__PURE__ */ new Map();
|
|
27409
|
+
let depth = /* @__PURE__ */ new Map();
|
|
27410
|
+
let assign = (start) => {
|
|
27411
|
+
let queue = [start];
|
|
27412
|
+
depth.set(start, 0);
|
|
27413
|
+
while (queue.length) {
|
|
27414
|
+
let id = queue.shift();
|
|
27415
|
+
let kids = [];
|
|
27416
|
+
for (let link of out.get(id) || []) {
|
|
27417
|
+
if (depth.has(link.id)) continue;
|
|
27418
|
+
depth.set(link.id, depth.get(id) + 1);
|
|
27419
|
+
kids.push(link.id);
|
|
27420
|
+
queue.push(link.id);
|
|
27421
|
+
}
|
|
27422
|
+
children.set(id, kids);
|
|
27423
|
+
}
|
|
27424
|
+
};
|
|
27425
|
+
for (let root of roots) assign(root);
|
|
27426
|
+
for (let n of by_id.values()) if (!depth.has(n.id)) {
|
|
27427
|
+
roots.push(n.id);
|
|
27428
|
+
assign(n.id);
|
|
27429
|
+
}
|
|
27430
|
+
return {
|
|
27431
|
+
roots,
|
|
27432
|
+
children,
|
|
27433
|
+
depth,
|
|
27434
|
+
by_id
|
|
27435
|
+
};
|
|
27436
|
+
}
|
|
27437
|
+
/************************************************************
|
|
27438
|
+
* The tidy tree, left to right.
|
|
27439
|
+
*
|
|
27440
|
+
* Returns Map<id, {x, y}> with the CENTRE of each node, the way G6
|
|
27441
|
+
* positions a node.
|
|
27442
|
+
************************************************************/
|
|
27443
|
+
function layout_tree(nodes, edges, opts) {
|
|
27444
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
27445
|
+
let t = spanning_tree(nodes, edges);
|
|
27446
|
+
let pos = /* @__PURE__ */ new Map();
|
|
27447
|
+
let col_w = [];
|
|
27448
|
+
for (let [id, d] of t.depth) {
|
|
27449
|
+
let w = t.by_id.get(id).w;
|
|
27450
|
+
col_w[d] = Math.max(col_w[d] || 0, w);
|
|
27451
|
+
}
|
|
27452
|
+
let col_x = [];
|
|
27453
|
+
let x = 0;
|
|
27454
|
+
for (let d = 0; d < col_w.length; d++) {
|
|
27455
|
+
col_x[d] = x;
|
|
27456
|
+
x += (col_w[d] || 0) + o.ranksep;
|
|
27457
|
+
}
|
|
27458
|
+
let height = /* @__PURE__ */ new Map();
|
|
27459
|
+
let measure = (id) => {
|
|
27460
|
+
let kids = t.children.get(id) || [];
|
|
27461
|
+
let own = t.by_id.get(id).h;
|
|
27462
|
+
if (!kids.length) {
|
|
27463
|
+
height.set(id, own);
|
|
27464
|
+
return own;
|
|
27465
|
+
}
|
|
27466
|
+
let sum = 0;
|
|
27467
|
+
for (let i = 0; i < kids.length; i++) {
|
|
27468
|
+
sum += measure(kids[i]);
|
|
27469
|
+
if (i > 0) sum += o.nodesep;
|
|
27470
|
+
}
|
|
27471
|
+
let h = Math.max(own, sum);
|
|
27472
|
+
height.set(id, h);
|
|
27473
|
+
return h;
|
|
27474
|
+
};
|
|
27475
|
+
for (let root of t.roots) measure(root);
|
|
27476
|
+
let place = (id, top) => {
|
|
27477
|
+
let n = t.by_id.get(id);
|
|
27478
|
+
let d = t.depth.get(id);
|
|
27479
|
+
let kids = t.children.get(id) || [];
|
|
27480
|
+
let block = height.get(id);
|
|
27481
|
+
let cx = col_x[d] + (col_w[d] - n.w) / 2 + n.w / 2;
|
|
27482
|
+
if (!kids.length) {
|
|
27483
|
+
pos.set(id, {
|
|
27484
|
+
x: cx,
|
|
27485
|
+
y: top + block / 2
|
|
27486
|
+
});
|
|
27487
|
+
return;
|
|
27488
|
+
}
|
|
27489
|
+
let kids_h = 0;
|
|
27490
|
+
for (let i = 0; i < kids.length; i++) {
|
|
27491
|
+
kids_h += height.get(kids[i]);
|
|
27492
|
+
if (i > 0) kids_h += o.nodesep;
|
|
27493
|
+
}
|
|
27494
|
+
let cursor = top + (block - kids_h) / 2;
|
|
27495
|
+
for (let kid of kids) {
|
|
27496
|
+
place(kid, cursor);
|
|
27497
|
+
cursor += height.get(kid) + o.nodesep;
|
|
27498
|
+
}
|
|
27499
|
+
pos.set(id, {
|
|
27500
|
+
x: cx,
|
|
27501
|
+
y: top + block / 2
|
|
27502
|
+
});
|
|
27503
|
+
};
|
|
27504
|
+
let top = 0;
|
|
27505
|
+
for (let root of t.roots) {
|
|
27506
|
+
place(root, top);
|
|
27507
|
+
top += height.get(root) + o.nodesep * 2;
|
|
27508
|
+
}
|
|
27509
|
+
return pos;
|
|
27510
|
+
}
|
|
27511
|
+
/************************************************************
|
|
27512
|
+
* The radial tree: the root in the middle, a ring per depth.
|
|
27513
|
+
*
|
|
27514
|
+
* G6's `radial` places by MDS on the graph distances and pushes
|
|
27515
|
+
* overlaps apart afterwards, with a ring radius it is TOLD; twenty
|
|
27516
|
+
* cards of 172px on a ring of radius 200 have 52px each and pile
|
|
27517
|
+
* up. This one is the radial form of the tidy tree above: every
|
|
27518
|
+
* subtree gets an angular SECTOR proportional to its leaves, a node
|
|
27519
|
+
* sits in the middle of its sector, and the radius of each ring is
|
|
27520
|
+
* the larger of "one step further out than the ring before" and
|
|
27521
|
+
* "long enough for its nodes to sit side by side" -- so a hall
|
|
27522
|
+
* with a hundred devices is a wide, sparse fan, and nothing
|
|
27523
|
+
* overlaps by construction. Several roots share the circle around
|
|
27524
|
+
* a virtual centre.
|
|
27525
|
+
*
|
|
27526
|
+
* ranksep the step between rings
|
|
27527
|
+
* nodesep the gap between neighbours on a ring
|
|
27528
|
+
************************************************************/
|
|
27529
|
+
function layout_radial(nodes, edges, opts) {
|
|
27530
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
27531
|
+
let t = spanning_tree(nodes, edges);
|
|
27532
|
+
let pos = /* @__PURE__ */ new Map();
|
|
27533
|
+
if (!t.roots.length) return pos;
|
|
27534
|
+
let leaves = /* @__PURE__ */ new Map();
|
|
27535
|
+
let count = (id) => {
|
|
27536
|
+
let kids = t.children.get(id) || [];
|
|
27537
|
+
if (!kids.length) {
|
|
27538
|
+
leaves.set(id, 1);
|
|
27539
|
+
return 1;
|
|
27540
|
+
}
|
|
27541
|
+
let n = 0;
|
|
27542
|
+
for (let kid of kids) n += count(kid);
|
|
27543
|
+
leaves.set(id, n);
|
|
27544
|
+
return n;
|
|
27545
|
+
};
|
|
27546
|
+
let total = 0;
|
|
27547
|
+
for (let root of t.roots) total += count(root);
|
|
27548
|
+
let single = t.roots.length === 1;
|
|
27549
|
+
let ring_of = (id) => t.depth.get(id) + (single ? 0 : 1);
|
|
27550
|
+
let angle = /* @__PURE__ */ new Map();
|
|
27551
|
+
let rings = [];
|
|
27552
|
+
let sector = (id, from, span) => {
|
|
27553
|
+
let a = from + span / 2;
|
|
27554
|
+
angle.set(id, a);
|
|
27555
|
+
let ring = ring_of(id);
|
|
27556
|
+
if (!rings[ring]) rings[ring] = [];
|
|
27557
|
+
let n = t.by_id.get(id);
|
|
27558
|
+
rings[ring].push({
|
|
27559
|
+
id,
|
|
27560
|
+
angle: a,
|
|
27561
|
+
diag: Math.hypot(n.w, n.h)
|
|
27562
|
+
});
|
|
27563
|
+
let cursor = from;
|
|
27564
|
+
for (let kid of t.children.get(id) || []) {
|
|
27565
|
+
let part = span * leaves.get(kid) / leaves.get(id);
|
|
27566
|
+
sector(kid, cursor, part);
|
|
27567
|
+
cursor += part;
|
|
27568
|
+
}
|
|
27569
|
+
};
|
|
27570
|
+
if (single) {
|
|
27571
|
+
let root = t.roots[0];
|
|
27572
|
+
angle.set(root, 0);
|
|
27573
|
+
let cursor = -Math.PI / 2;
|
|
27574
|
+
for (let kid of t.children.get(root) || []) {
|
|
27575
|
+
let part = 2 * Math.PI * leaves.get(kid) / leaves.get(root);
|
|
27576
|
+
sector(kid, cursor, part);
|
|
27577
|
+
cursor += part;
|
|
27578
|
+
}
|
|
27579
|
+
} else {
|
|
27580
|
+
let cursor = -Math.PI / 2;
|
|
27581
|
+
for (let root of t.roots) {
|
|
27582
|
+
let part = 2 * Math.PI * leaves.get(root) / total;
|
|
27583
|
+
sector(root, cursor, part);
|
|
27584
|
+
cursor += part;
|
|
27585
|
+
}
|
|
27586
|
+
}
|
|
27587
|
+
let radius = [0];
|
|
27588
|
+
for (let ring = 1; ring < rings.length; ring++) {
|
|
27589
|
+
let r = radius[ring - 1] + o.ranksep;
|
|
27590
|
+
let list = (rings[ring] || []).slice().sort((a, b) => a.angle - b.angle);
|
|
27591
|
+
for (let i = 0; i < list.length && list.length > 1; i++) {
|
|
27592
|
+
let a = list[i];
|
|
27593
|
+
let b = list[(i + 1) % list.length];
|
|
27594
|
+
let gap = i + 1 < list.length ? b.angle - a.angle : b.angle + 2 * Math.PI - a.angle;
|
|
27595
|
+
if (gap > 1e-9) r = Math.max(r, (a.diag / 2 + b.diag / 2 + o.nodesep) / gap);
|
|
27596
|
+
}
|
|
27597
|
+
radius[ring] = r;
|
|
27598
|
+
}
|
|
27599
|
+
for (let [id, a] of angle) {
|
|
27600
|
+
let r = radius[ring_of(id)] || 0;
|
|
27601
|
+
pos.set(id, {
|
|
27602
|
+
x: r * Math.cos(a),
|
|
27603
|
+
y: r * Math.sin(a)
|
|
27604
|
+
});
|
|
27605
|
+
}
|
|
27606
|
+
return pos;
|
|
27607
|
+
}
|
|
27608
|
+
/************************************************************
|
|
27609
|
+
* The outline: one node per row, indented by depth, in the
|
|
27610
|
+
* pre-order of the spanning tree. Rows are left-aligned on
|
|
27611
|
+
* their indent, so a column of chips reads as a list.
|
|
27612
|
+
************************************************************/
|
|
27613
|
+
function layout_outline(nodes, edges, opts) {
|
|
27614
|
+
let o = Object.assign({}, DEFAULTS, opts || {});
|
|
27615
|
+
let t = spanning_tree(nodes, edges);
|
|
27616
|
+
let pos = /* @__PURE__ */ new Map();
|
|
27617
|
+
let y = 0;
|
|
27618
|
+
let visit = (id) => {
|
|
27619
|
+
let n = t.by_id.get(id);
|
|
27620
|
+
let d = t.depth.get(id);
|
|
27621
|
+
pos.set(id, {
|
|
27622
|
+
x: d * o.indent + n.w / 2,
|
|
27623
|
+
y: y + n.h / 2
|
|
27624
|
+
});
|
|
27625
|
+
y += n.h + o.nodesep;
|
|
27626
|
+
for (let kid of t.children.get(id) || []) visit(kid);
|
|
27627
|
+
};
|
|
27628
|
+
for (let root of t.roots) {
|
|
27629
|
+
visit(root);
|
|
27630
|
+
y += o.nodesep;
|
|
27631
|
+
}
|
|
27632
|
+
return pos;
|
|
27633
|
+
}
|
|
27634
|
+
//#endregion
|
|
27312
27635
|
//#region node_modules/@babel/runtime/helpers/esm/typeof.js
|
|
27313
27636
|
function _typeof(o) {
|
|
27314
27637
|
"@babel/helpers - typeof";
|
|
@@ -54129,6 +54452,16 @@ var SELECT_RING = "rgba(59,130,246,0.95)";
|
|
|
54129
54452
|
***************************************************************/
|
|
54130
54453
|
var _layouts = {
|
|
54131
54454
|
"manual": { type: "manual" },
|
|
54455
|
+
"treedb-tree": {
|
|
54456
|
+
type: "treedb-tree",
|
|
54457
|
+
nodesep: 18,
|
|
54458
|
+
ranksep: 110
|
|
54459
|
+
},
|
|
54460
|
+
"treedb-outline": {
|
|
54461
|
+
type: "treedb-outline",
|
|
54462
|
+
nodesep: 12,
|
|
54463
|
+
indent: 48
|
|
54464
|
+
},
|
|
54132
54465
|
"dagre": {
|
|
54133
54466
|
type: "dagre",
|
|
54134
54467
|
rankdir: "LR",
|
|
@@ -54141,6 +54474,11 @@ var _layouts = {
|
|
|
54141
54474
|
nodesep: 30,
|
|
54142
54475
|
ranksep: 80
|
|
54143
54476
|
},
|
|
54477
|
+
"radial": {
|
|
54478
|
+
type: "treedb-radial",
|
|
54479
|
+
nodesep: 24,
|
|
54480
|
+
ranksep: 180
|
|
54481
|
+
},
|
|
54144
54482
|
"d3-force": {
|
|
54145
54483
|
type: "d3-force",
|
|
54146
54484
|
link: {
|
|
@@ -54420,6 +54758,9 @@ function register_layouts(gobj) {
|
|
|
54420
54758
|
if (!_g6_extensions_registered) {
|
|
54421
54759
|
_g6_extensions_registered = true;
|
|
54422
54760
|
register(ExtensionCategory.LAYOUT, "manual", ManualLayout);
|
|
54761
|
+
register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
|
|
54762
|
+
register(ExtensionCategory.LAYOUT, "treedb-outline", TreedbOutlineLayout);
|
|
54763
|
+
register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
|
|
54423
54764
|
register(ExtensionCategory.NODE, "light", LightNode);
|
|
54424
54765
|
}
|
|
54425
54766
|
}
|
|
@@ -55031,8 +55372,13 @@ function build_ports(gobj, desc) {
|
|
|
55031
55372
|
* included, because the saved arrangements were made with the
|
|
55032
55373
|
* ports on the top and bottom edges.
|
|
55033
55374
|
************************************************************/
|
|
55375
|
+
var LR_LAYOUTS = [
|
|
55376
|
+
"dagre",
|
|
55377
|
+
"treedb-tree",
|
|
55378
|
+
"treedb-outline"
|
|
55379
|
+
];
|
|
55034
55380
|
function layout_direction(gobj) {
|
|
55035
|
-
return gobj.priv.layout
|
|
55381
|
+
return str_in_list(LR_LAYOUTS, gobj.priv.layout) ? "LR" : "TB";
|
|
55036
55382
|
}
|
|
55037
55383
|
function edge_type_for(gobj) {
|
|
55038
55384
|
return layout_direction(gobj) === "LR" ? "cubic-horizontal" : "cubic";
|
|
@@ -56032,6 +56378,73 @@ var ManualLayout = class extends BaseLayout {
|
|
|
56032
56378
|
}
|
|
56033
56379
|
};
|
|
56034
56380
|
/************************************************************
|
|
56381
|
+
* Custom G6 layouts: TreedbTreeLayout / TreedbOutlineLayout.
|
|
56382
|
+
*
|
|
56383
|
+
* Thin adapters over treedb_layout.js: the nodes with their sizes,
|
|
56384
|
+
* the edges with the RANK of the hook they leave by (the position
|
|
56385
|
+
* of the source port in the parent's ports, which is the schema's
|
|
56386
|
+
* column order), and the positions back. A `+N` chip's edge has a
|
|
56387
|
+
* source port too, so the chip sits at the end of its hook's
|
|
56388
|
+
* children, where the page ends.
|
|
56389
|
+
************************************************************/
|
|
56390
|
+
function treedb_layout_input(data) {
|
|
56391
|
+
const { nodes = [], edges = [] } = data;
|
|
56392
|
+
let ports_of = /* @__PURE__ */ new Map();
|
|
56393
|
+
return {
|
|
56394
|
+
nodes: nodes.map((node) => {
|
|
56395
|
+
let size = node.style && node.style.size || [172, 96];
|
|
56396
|
+
let w = is_array(size) ? size[0] : size;
|
|
56397
|
+
let h = is_array(size) ? size.length > 1 ? size[1] : size[0] : size;
|
|
56398
|
+
let ports = node.style && is_array(node.style.ports) ? node.style.ports : [];
|
|
56399
|
+
ports_of.set(node.id, ports.map((p) => p.key));
|
|
56400
|
+
return {
|
|
56401
|
+
id: node.id,
|
|
56402
|
+
w,
|
|
56403
|
+
h
|
|
56404
|
+
};
|
|
56405
|
+
}),
|
|
56406
|
+
edges: edges.map((edge) => {
|
|
56407
|
+
let port = edge.style ? edge.style.sourcePort : void 0;
|
|
56408
|
+
let keys = ports_of.get(edge.source) || [];
|
|
56409
|
+
let rank = port ? keys.indexOf(port) : -1;
|
|
56410
|
+
return {
|
|
56411
|
+
source: edge.source,
|
|
56412
|
+
target: edge.target,
|
|
56413
|
+
rank: rank >= 0 ? rank : 0
|
|
56414
|
+
};
|
|
56415
|
+
})
|
|
56416
|
+
};
|
|
56417
|
+
}
|
|
56418
|
+
function treedb_layout_output(pos) {
|
|
56419
|
+
let out = [];
|
|
56420
|
+
for (let [id, p] of pos) out.push({
|
|
56421
|
+
id,
|
|
56422
|
+
style: {
|
|
56423
|
+
x: p.x,
|
|
56424
|
+
y: p.y
|
|
56425
|
+
}
|
|
56426
|
+
});
|
|
56427
|
+
return { nodes: out };
|
|
56428
|
+
}
|
|
56429
|
+
var TreedbTreeLayout = class extends BaseLayout {
|
|
56430
|
+
async execute(data, options) {
|
|
56431
|
+
let input = treedb_layout_input(data);
|
|
56432
|
+
return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
|
|
56433
|
+
}
|
|
56434
|
+
};
|
|
56435
|
+
var TreedbOutlineLayout = class extends BaseLayout {
|
|
56436
|
+
async execute(data, options) {
|
|
56437
|
+
let input = treedb_layout_input(data);
|
|
56438
|
+
return treedb_layout_output(layout_outline(input.nodes, input.edges, options));
|
|
56439
|
+
}
|
|
56440
|
+
};
|
|
56441
|
+
var TreedbRadialLayout = class extends BaseLayout {
|
|
56442
|
+
async execute(data, options) {
|
|
56443
|
+
let input = treedb_layout_input(data);
|
|
56444
|
+
return treedb_layout_output(layout_radial(input.nodes, input.edges, options));
|
|
56445
|
+
}
|
|
56446
|
+
};
|
|
56447
|
+
/************************************************************
|
|
56035
56448
|
* Node selection and resize handles
|
|
56036
56449
|
************************************************************/
|
|
56037
56450
|
var RESIZE_HANDLE_SIZE = 8;
|
|
@@ -57693,7 +58106,7 @@ function auto_layout(gobj) {
|
|
|
57693
58106
|
if (priv._layout_asked) return false;
|
|
57694
58107
|
if (priv._nodes_total > 0 && priv._nodes_placed * 2 > priv._nodes_total) return false;
|
|
57695
58108
|
if (!priv.graph) return false;
|
|
57696
|
-
let name = "
|
|
58109
|
+
let name = "treedb-tree";
|
|
57697
58110
|
gobj_write_attr(gobj, "layout", name);
|
|
57698
58111
|
try {
|
|
57699
58112
|
priv.graph.setLayout(_layouts[name]);
|
|
@@ -60530,6 +60943,9 @@ function option_label(name) {
|
|
|
60530
60943
|
case "writing": return t("writing");
|
|
60531
60944
|
case "edition": return t("edition");
|
|
60532
60945
|
case "manual": return t("manual");
|
|
60946
|
+
case "treedb-tree": return t("treedb-tree");
|
|
60947
|
+
case "treedb-outline": return t("treedb-outline");
|
|
60948
|
+
case "radial": return t("radial");
|
|
60533
60949
|
case "dagre": return t("dagre");
|
|
60534
60950
|
case "antv-dagre": return t("antv-dagre");
|
|
60535
60951
|
case "d3-force": return t("d3-force");
|
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -124,6 +124,7 @@ import {
|
|
|
124
124
|
fold_node_key,
|
|
125
125
|
fold_split_group_key,
|
|
126
126
|
} from "./treedb_fold_model.js";
|
|
127
|
+
import {layout_tree, layout_outline, layout_radial} from "./treedb_layout.js";
|
|
127
128
|
|
|
128
129
|
import {
|
|
129
130
|
BaseLayout,
|
|
@@ -260,6 +261,23 @@ const _layouts = {
|
|
|
260
261
|
* cards edge to edge. `antv-dagre` below keeps the top-down reading
|
|
261
262
|
* for whoever wants it -- the two are the same algorithm read two
|
|
262
263
|
* ways, so the layout picker is also the direction picker. */
|
|
264
|
+
/* OURS, made for a treedb (treedb_layout.js): a tidy tree read
|
|
265
|
+
* left to right, and an outline of one row per node. Both take
|
|
266
|
+
* the spanning tree of what is on screen -- the roots, the first
|
|
267
|
+
* parent that reaches a node, the hooks in schema order, the
|
|
268
|
+
* records in their order -- so opening a hook moves nothing that
|
|
269
|
+
* is not under or beside it. The default for a treedb nobody has
|
|
270
|
+
* arranged (see auto_layout). */
|
|
271
|
+
"treedb-tree": {
|
|
272
|
+
type: 'treedb-tree',
|
|
273
|
+
nodesep: 18,
|
|
274
|
+
ranksep: 110,
|
|
275
|
+
},
|
|
276
|
+
"treedb-outline": {
|
|
277
|
+
type: 'treedb-outline',
|
|
278
|
+
nodesep: 12,
|
|
279
|
+
indent: 48,
|
|
280
|
+
},
|
|
263
281
|
"dagre": {
|
|
264
282
|
type: 'dagre',
|
|
265
283
|
rankdir: 'LR',
|
|
@@ -272,6 +290,14 @@ const _layouts = {
|
|
|
272
290
|
nodesep: 30,
|
|
273
291
|
ranksep: 80,
|
|
274
292
|
},
|
|
293
|
+
/* Ours too: the root in the middle, a ring per depth, every ring
|
|
294
|
+
* as long as its cards need. G6's `radial` was tried first and
|
|
295
|
+
* piled twenty cards of 172px on a ring of a fixed radius. */
|
|
296
|
+
"radial": {
|
|
297
|
+
type: 'treedb-radial',
|
|
298
|
+
nodesep: 24,
|
|
299
|
+
ranksep: 180,
|
|
300
|
+
},
|
|
275
301
|
"d3-force": {
|
|
276
302
|
type: 'd3-force',
|
|
277
303
|
link: {
|
|
@@ -780,6 +806,9 @@ function register_layouts(gobj)
|
|
|
780
806
|
if(!_g6_extensions_registered) {
|
|
781
807
|
_g6_extensions_registered = true;
|
|
782
808
|
register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
|
|
809
|
+
register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
|
|
810
|
+
register(ExtensionCategory.LAYOUT, 'treedb-outline', TreedbOutlineLayout);
|
|
811
|
+
register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
|
|
783
812
|
register(ExtensionCategory.NODE, 'light', LightNode);
|
|
784
813
|
}
|
|
785
814
|
}
|
|
@@ -1765,9 +1794,11 @@ function build_ports(gobj, desc)
|
|
|
1765
1794
|
* included, because the saved arrangements were made with the
|
|
1766
1795
|
* ports on the top and bottom edges.
|
|
1767
1796
|
************************************************************/
|
|
1797
|
+
const LR_LAYOUTS = ["dagre", "treedb-tree", "treedb-outline"];
|
|
1798
|
+
|
|
1768
1799
|
function layout_direction(gobj)
|
|
1769
1800
|
{
|
|
1770
|
-
return (gobj.priv.layout
|
|
1801
|
+
return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
|
|
1771
1802
|
}
|
|
1772
1803
|
|
|
1773
1804
|
function edge_type_for(gobj)
|
|
@@ -3304,6 +3335,73 @@ class ManualLayout extends BaseLayout
|
|
|
3304
3335
|
}
|
|
3305
3336
|
}
|
|
3306
3337
|
|
|
3338
|
+
/************************************************************
|
|
3339
|
+
* Custom G6 layouts: TreedbTreeLayout / TreedbOutlineLayout.
|
|
3340
|
+
*
|
|
3341
|
+
* Thin adapters over treedb_layout.js: the nodes with their sizes,
|
|
3342
|
+
* the edges with the RANK of the hook they leave by (the position
|
|
3343
|
+
* of the source port in the parent's ports, which is the schema's
|
|
3344
|
+
* column order), and the positions back. A `+N` chip's edge has a
|
|
3345
|
+
* source port too, so the chip sits at the end of its hook's
|
|
3346
|
+
* children, where the page ends.
|
|
3347
|
+
************************************************************/
|
|
3348
|
+
function treedb_layout_input(data)
|
|
3349
|
+
{
|
|
3350
|
+
const {nodes = [], edges = []} = data;
|
|
3351
|
+
|
|
3352
|
+
let ports_of = new Map();
|
|
3353
|
+
let in_nodes = nodes.map((node) => {
|
|
3354
|
+
let size = (node.style && node.style.size) || [172, 96];
|
|
3355
|
+
let w = is_array(size)? size[0] : size;
|
|
3356
|
+
let h = is_array(size)? (size.length > 1? size[1] : size[0]) : size;
|
|
3357
|
+
let ports = (node.style && is_array(node.style.ports))? node.style.ports : [];
|
|
3358
|
+
ports_of.set(node.id, ports.map((p) => p.key));
|
|
3359
|
+
return {id: node.id, w: w, h: h};
|
|
3360
|
+
});
|
|
3361
|
+
|
|
3362
|
+
let in_edges = edges.map((edge) => {
|
|
3363
|
+
let port = edge.style? edge.style.sourcePort : undefined;
|
|
3364
|
+
let keys = ports_of.get(edge.source) || [];
|
|
3365
|
+
let rank = port? keys.indexOf(port) : -1;
|
|
3366
|
+
return {source: edge.source, target: edge.target, rank: (rank >= 0)? rank : 0};
|
|
3367
|
+
});
|
|
3368
|
+
|
|
3369
|
+
return {nodes: in_nodes, edges: in_edges};
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
function treedb_layout_output(pos)
|
|
3373
|
+
{
|
|
3374
|
+
let out = [];
|
|
3375
|
+
for(let [id, p] of pos) {
|
|
3376
|
+
out.push({id: id, style: {x: p.x, y: p.y}});
|
|
3377
|
+
}
|
|
3378
|
+
return {nodes: out};
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
class TreedbTreeLayout extends BaseLayout
|
|
3382
|
+
{
|
|
3383
|
+
async execute(data, options) {
|
|
3384
|
+
let input = treedb_layout_input(data);
|
|
3385
|
+
return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
class TreedbOutlineLayout extends BaseLayout
|
|
3390
|
+
{
|
|
3391
|
+
async execute(data, options) {
|
|
3392
|
+
let input = treedb_layout_input(data);
|
|
3393
|
+
return treedb_layout_output(layout_outline(input.nodes, input.edges, options));
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
class TreedbRadialLayout extends BaseLayout
|
|
3398
|
+
{
|
|
3399
|
+
async execute(data, options) {
|
|
3400
|
+
let input = treedb_layout_input(data);
|
|
3401
|
+
return treedb_layout_output(layout_radial(input.nodes, input.edges, options));
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3307
3405
|
/************************************************************
|
|
3308
3406
|
* Node selection and resize handles
|
|
3309
3407
|
************************************************************/
|
|
@@ -5860,7 +5958,10 @@ function auto_layout(gobj)
|
|
|
5860
5958
|
return false;
|
|
5861
5959
|
}
|
|
5862
5960
|
|
|
5863
|
-
|
|
5961
|
+
/* Ours, not dagre: the tidy tree keeps the records in their order
|
|
5962
|
+
* and moves nothing on a fold that is not under it (see
|
|
5963
|
+
* treedb_layout.js). dagre stays in the picker. */
|
|
5964
|
+
let name = "treedb-tree";
|
|
5864
5965
|
gobj_write_attr(gobj, "layout", name);
|
|
5865
5966
|
try {
|
|
5866
5967
|
priv.graph.setLayout(_layouts[name]);
|
|
@@ -506,6 +506,12 @@ function option_label(name)
|
|
|
506
506
|
/* Layouts (`layout_names` of the G6 child). */
|
|
507
507
|
case "manual":
|
|
508
508
|
return t("manual");
|
|
509
|
+
case "treedb-tree":
|
|
510
|
+
return t("treedb-tree");
|
|
511
|
+
case "treedb-outline":
|
|
512
|
+
return t("treedb-outline");
|
|
513
|
+
case "radial":
|
|
514
|
+
return t("radial");
|
|
509
515
|
case "dagre":
|
|
510
516
|
return t("dagre");
|
|
511
517
|
case "antv-dagre":
|