@yuneta/gobj-ui 7.23.68 → 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.
@@ -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 === "dagre" ? "LR" : "TB";
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 = "dagre";
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");
@@ -61633,6 +62049,7 @@ function ac_set_operation_mode(gobj, event, kw, src) {
61633
62049
  * card's graph icon travels — as EV_TOPIC_SELECTED, which the host turns
61634
62050
  * into the URL, so what you are looking at stays linkable.
61635
62051
  ************************************************************/
62052
+ var LEGEND_GLYPH_STYLE = "font-size:1.5em; line-height:1; color:inherit;";
61636
62053
  function refresh_legend(gobj) {
61637
62054
  let priv = gobj.priv;
61638
62055
  let $container = gobj_read_attr(gobj, "$container");
@@ -61702,7 +62119,11 @@ function refresh_legend(gobj) {
61702
62119
  "aria-label": t("main topic"),
61703
62120
  "aria-pressed": "true"
61704
62121
  },
61705
- "★",
62122
+ [[
62123
+ "span",
62124
+ { style: LEGEND_GLYPH_STYLE },
62125
+ "★"
62126
+ ]],
61706
62127
  { click: (evt) => {
61707
62128
  evt.stopPropagation();
61708
62129
  gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
@@ -61719,7 +62140,11 @@ function refresh_legend(gobj) {
61719
62140
  title: t("main topic"),
61720
62141
  "data-i18n-title": "main topic"
61721
62142
  },
61722
- "★"
62143
+ [[
62144
+ "span",
62145
+ { style: LEGEND_GLYPH_STYLE },
62146
+ "★"
62147
+ ]]
61723
62148
  ]);
61724
62149
  else if (!hidden) extras.push([
61725
62150
  "button",
@@ -61731,7 +62156,11 @@ function refresh_legend(gobj) {
61731
62156
  "data-i18n-title": "main topic",
61732
62157
  "aria-label": t("main topic")
61733
62158
  },
61734
- "☆",
62159
+ [[
62160
+ "span",
62161
+ { style: LEGEND_GLYPH_STYLE },
62162
+ "☆"
62163
+ ]],
61735
62164
  { click: (evt) => {
61736
62165
  evt.stopPropagation();
61737
62166
  gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
@@ -61771,7 +62200,10 @@ function refresh_legend(gobj) {
61771
62200
  "aria-label": t("highlight topic"),
61772
62201
  "aria-pressed": focused ? "true" : "false"
61773
62202
  },
61774
- [["i", { class: "yi-location-crosshairs" }]],
62203
+ [["i", {
62204
+ class: "yi-location-crosshairs",
62205
+ style: LEGEND_GLYPH_STYLE
62206
+ }]],
61775
62207
  { click: (evt) => {
61776
62208
  evt.stopPropagation();
61777
62209
  gobj_send_event(gobj, "EV_LEGEND_TOPIC", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.68",
3
+ "version": "7.23.70",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -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 === "dagre")? "LR" : "TB";
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
- let name = "dagre";
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]);