@yuneta/gobj-ui 7.23.72 → 7.23.73
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 +29 -0
- package/dist/gobj-ui.cjs.js +105 -74
- package/dist/gobj-ui.es.js +105 -74
- package/package.json +1 -1
- package/src/c_yui_treedb_graph.js +16 -6
- package/src/treedb_layout.js +95 -60
- package/src/treedb_layout.test.js +32 -0
package/README.md
CHANGED
|
@@ -1882,6 +1882,35 @@ the middle, and **a toolbar left with a single group is centred**. An unknown
|
|
|
1882
1882
|
name is reported, not silently dropped: a typo would otherwise remove the save
|
|
1883
1883
|
button with no trace of why.
|
|
1884
1884
|
|
|
1885
|
+
### `C_YUI_FORM` — which field is editable, and the one `writable` does not govern
|
|
1886
|
+
|
|
1887
|
+
A form opened to LOOK at a record has no editable field: that is a property of
|
|
1888
|
+
the opening (`readonly`), not of the schema, and it wins over everything. Under
|
|
1889
|
+
it the schema decides — a column needs `writable` — **with one exception, and
|
|
1890
|
+
it is the whole of linking: an fkey.**
|
|
1891
|
+
|
|
1892
|
+
`writable` governs the WRITE of a column's VALUE, and an fkey is not written,
|
|
1893
|
+
it is **linked**. An fkey is normally declared with no `writable` flag at all
|
|
1894
|
+
(`treedb_authzs`'s `users.roles` is `['fkey']`, and so is almost every fkey in
|
|
1895
|
+
the tree), so reading "not writable" as "not editable" takes away the only way
|
|
1896
|
+
to link a record to its parent — with `users.roles`, the only way to give a
|
|
1897
|
+
person a role. The other half of the same rule is in the topic view, where the
|
|
1898
|
+
record travels back: it sends "the writable cols, the fkeys (a link is edited
|
|
1899
|
+
by linking) and the pkey". The two halves have to agree; when they did not, the
|
|
1900
|
+
form disabled the control whose value the save was still waiting for.
|
|
1901
|
+
|
|
1902
|
+
The rule lives in `form_field_readonly.js`, pure and tested. A `file` column is
|
|
1903
|
+
an fkey too (`['fkey','file']`) and is deliberately NOT covered: it answers
|
|
1904
|
+
`type: "file"`, its bytes travel beside the record, and the SDK declares as
|
|
1905
|
+
legal a `file` column that only a load fills.
|
|
1906
|
+
|
|
1907
|
+
**And `readonly` is an attribute of a TEXT control and of nothing else.** A
|
|
1908
|
+
`<select>`, a checkbox and a radio accept it and let the reader change the
|
|
1909
|
+
value anyway; only `disabled` stops them, and tom-select has to be told through
|
|
1910
|
+
its API. So the form applies `disabled` to those three, and the record is still
|
|
1911
|
+
read from a disabled control — it is collected by reading each marked control,
|
|
1912
|
+
never by submitting the form.
|
|
1913
|
+
|
|
1885
1914
|
### Selecting rows in any table — `yui_table_select.js`
|
|
1886
1915
|
|
|
1887
1916
|
Deleting twenty rows one confirmation at a time is not a workflow. Any view
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -27492,6 +27492,28 @@ function spanning_tree(nodes, edges) {
|
|
|
27492
27492
|
};
|
|
27493
27493
|
}
|
|
27494
27494
|
/************************************************************
|
|
27495
|
+
* The ids of a spanning tree in PARENT-BEFORE-CHILD order.
|
|
27496
|
+
*
|
|
27497
|
+
* Every pass below used to be a recursive walk, which is the natural
|
|
27498
|
+
* way to write it and the one that dies on a deep tree: a
|
|
27499
|
+
* self-referent hook (a place inside a place inside a place) is as
|
|
27500
|
+
* deep as the data says, and the stack is not. Read this list
|
|
27501
|
+
* forwards for a pre-order pass and BACKWARDS for a post-order one
|
|
27502
|
+
* -- a child always sits after its parent, so reversed it always
|
|
27503
|
+
* sits before it, which is the whole of what post-order was for.
|
|
27504
|
+
************************************************************/
|
|
27505
|
+
function walk_order(t) {
|
|
27506
|
+
let order = [];
|
|
27507
|
+
let stack = t.roots.slice().reverse();
|
|
27508
|
+
while (stack.length) {
|
|
27509
|
+
let id = stack.pop();
|
|
27510
|
+
order.push(id);
|
|
27511
|
+
let kids = t.children.get(id) || [];
|
|
27512
|
+
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
|
|
27513
|
+
}
|
|
27514
|
+
return order;
|
|
27515
|
+
}
|
|
27516
|
+
/************************************************************
|
|
27495
27517
|
* The tidy tree, left to right.
|
|
27496
27518
|
*
|
|
27497
27519
|
* Returns Map<id, {x, y}> with the CENTRE of each node, the way G6
|
|
@@ -27512,56 +27534,45 @@ function layout_tree(nodes, edges, opts) {
|
|
|
27512
27534
|
col_x[d] = x;
|
|
27513
27535
|
x += (col_w[d] || 0) + o.ranksep;
|
|
27514
27536
|
}
|
|
27537
|
+
let order = walk_order(t);
|
|
27515
27538
|
let height = /* @__PURE__ */ new Map();
|
|
27516
|
-
let
|
|
27539
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
27540
|
+
let id = order[i];
|
|
27517
27541
|
let kids = t.children.get(id) || [];
|
|
27518
27542
|
let own = t.by_id.get(id).h;
|
|
27519
|
-
if (!kids.length) {
|
|
27520
|
-
height.set(id, own);
|
|
27521
|
-
return own;
|
|
27522
|
-
}
|
|
27523
27543
|
let sum = 0;
|
|
27524
|
-
for (let
|
|
27525
|
-
sum +=
|
|
27526
|
-
if (
|
|
27544
|
+
for (let k = 0; k < kids.length; k++) {
|
|
27545
|
+
sum += height.get(kids[k]);
|
|
27546
|
+
if (k > 0) sum += o.nodesep;
|
|
27527
27547
|
}
|
|
27528
|
-
|
|
27529
|
-
|
|
27530
|
-
|
|
27531
|
-
|
|
27532
|
-
for (let root of t.roots)
|
|
27533
|
-
|
|
27534
|
-
|
|
27548
|
+
height.set(id, kids.length ? Math.max(own, sum) : own);
|
|
27549
|
+
}
|
|
27550
|
+
let tops = /* @__PURE__ */ new Map();
|
|
27551
|
+
let top = 0;
|
|
27552
|
+
for (let root of t.roots) {
|
|
27553
|
+
tops.set(root, top);
|
|
27554
|
+
top += height.get(root) + o.nodesep * 2;
|
|
27555
|
+
}
|
|
27556
|
+
for (let id of order) {
|
|
27535
27557
|
let d = t.depth.get(id);
|
|
27536
27558
|
let kids = t.children.get(id) || [];
|
|
27537
27559
|
let block = height.get(id);
|
|
27538
|
-
let
|
|
27539
|
-
|
|
27540
|
-
|
|
27541
|
-
|
|
27542
|
-
|
|
27543
|
-
|
|
27544
|
-
return;
|
|
27545
|
-
}
|
|
27560
|
+
let own_top = tops.get(id);
|
|
27561
|
+
pos.set(id, {
|
|
27562
|
+
x: col_x[d] + col_w[d] / 2,
|
|
27563
|
+
y: own_top + block / 2
|
|
27564
|
+
});
|
|
27565
|
+
if (!kids.length) continue;
|
|
27546
27566
|
let kids_h = 0;
|
|
27547
|
-
for (let
|
|
27548
|
-
kids_h += height.get(kids[
|
|
27549
|
-
if (
|
|
27567
|
+
for (let k = 0; k < kids.length; k++) {
|
|
27568
|
+
kids_h += height.get(kids[k]);
|
|
27569
|
+
if (k > 0) kids_h += o.nodesep;
|
|
27550
27570
|
}
|
|
27551
|
-
let cursor =
|
|
27571
|
+
let cursor = own_top + (block - kids_h) / 2;
|
|
27552
27572
|
for (let kid of kids) {
|
|
27553
|
-
|
|
27573
|
+
tops.set(kid, cursor);
|
|
27554
27574
|
cursor += height.get(kid) + o.nodesep;
|
|
27555
27575
|
}
|
|
27556
|
-
pos.set(id, {
|
|
27557
|
-
x: cx,
|
|
27558
|
-
y: top + block / 2
|
|
27559
|
-
});
|
|
27560
|
-
};
|
|
27561
|
-
let top = 0;
|
|
27562
|
-
for (let root of t.roots) {
|
|
27563
|
-
place(root, top);
|
|
27564
|
-
top += height.get(root) + o.nodesep * 2;
|
|
27565
27576
|
}
|
|
27566
27577
|
return pos;
|
|
27567
27578
|
}
|
|
@@ -27588,40 +27599,53 @@ function layout_radial(nodes, edges, opts) {
|
|
|
27588
27599
|
let t = spanning_tree(nodes, edges);
|
|
27589
27600
|
let pos = /* @__PURE__ */ new Map();
|
|
27590
27601
|
if (!t.roots.length) return pos;
|
|
27602
|
+
let order = walk_order(t);
|
|
27591
27603
|
let leaves = /* @__PURE__ */ new Map();
|
|
27592
|
-
let
|
|
27604
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
27605
|
+
let id = order[i];
|
|
27593
27606
|
let kids = t.children.get(id) || [];
|
|
27594
27607
|
if (!kids.length) {
|
|
27595
27608
|
leaves.set(id, 1);
|
|
27596
|
-
|
|
27609
|
+
continue;
|
|
27597
27610
|
}
|
|
27598
27611
|
let n = 0;
|
|
27599
|
-
for (let kid of kids) n +=
|
|
27612
|
+
for (let kid of kids) n += leaves.get(kid);
|
|
27600
27613
|
leaves.set(id, n);
|
|
27601
|
-
|
|
27602
|
-
};
|
|
27614
|
+
}
|
|
27603
27615
|
let total = 0;
|
|
27604
|
-
for (let root of t.roots) total +=
|
|
27616
|
+
for (let root of t.roots) total += leaves.get(root);
|
|
27605
27617
|
let single = t.roots.length === 1;
|
|
27606
27618
|
let ring_of = (id) => t.depth.get(id) + (single ? 0 : 1);
|
|
27607
27619
|
let angle = /* @__PURE__ */ new Map();
|
|
27608
27620
|
let rings = [];
|
|
27609
|
-
let sector = (
|
|
27610
|
-
let
|
|
27611
|
-
|
|
27612
|
-
|
|
27613
|
-
|
|
27614
|
-
|
|
27615
|
-
|
|
27616
|
-
id,
|
|
27617
|
-
|
|
27618
|
-
|
|
27619
|
-
|
|
27620
|
-
|
|
27621
|
-
|
|
27622
|
-
|
|
27623
|
-
|
|
27624
|
-
|
|
27621
|
+
let sector = (root_id, root_from, root_span) => {
|
|
27622
|
+
let stack = [{
|
|
27623
|
+
id: root_id,
|
|
27624
|
+
from: root_from,
|
|
27625
|
+
span: root_span
|
|
27626
|
+
}];
|
|
27627
|
+
while (stack.length) {
|
|
27628
|
+
let { id, from, span } = stack.pop();
|
|
27629
|
+
let a = from + span / 2;
|
|
27630
|
+
angle.set(id, a);
|
|
27631
|
+
let ring = ring_of(id);
|
|
27632
|
+
if (!rings[ring]) rings[ring] = [];
|
|
27633
|
+
let n = t.by_id.get(id);
|
|
27634
|
+
rings[ring].push({
|
|
27635
|
+
id,
|
|
27636
|
+
angle: a,
|
|
27637
|
+
diag: Math.hypot(n.w, n.h)
|
|
27638
|
+
});
|
|
27639
|
+
let cursor = from;
|
|
27640
|
+
for (let kid of t.children.get(id) || []) {
|
|
27641
|
+
let part = span * leaves.get(kid) / leaves.get(id);
|
|
27642
|
+
stack.push({
|
|
27643
|
+
id: kid,
|
|
27644
|
+
from: cursor,
|
|
27645
|
+
span: part
|
|
27646
|
+
});
|
|
27647
|
+
cursor += part;
|
|
27648
|
+
}
|
|
27625
27649
|
}
|
|
27626
27650
|
};
|
|
27627
27651
|
if (single) {
|
|
@@ -27672,15 +27696,20 @@ function layout_outline(nodes, edges, opts) {
|
|
|
27672
27696
|
let t = spanning_tree(nodes, edges);
|
|
27673
27697
|
let pos = /* @__PURE__ */ new Map();
|
|
27674
27698
|
let y = 0;
|
|
27675
|
-
let visit = (
|
|
27676
|
-
let
|
|
27677
|
-
|
|
27678
|
-
|
|
27679
|
-
|
|
27680
|
-
|
|
27681
|
-
|
|
27682
|
-
|
|
27683
|
-
|
|
27699
|
+
let visit = (root_id) => {
|
|
27700
|
+
let stack = [root_id];
|
|
27701
|
+
while (stack.length) {
|
|
27702
|
+
let id = stack.pop();
|
|
27703
|
+
let n = t.by_id.get(id);
|
|
27704
|
+
let d = t.depth.get(id);
|
|
27705
|
+
pos.set(id, {
|
|
27706
|
+
x: d * o.indent + n.w / 2,
|
|
27707
|
+
y: y + n.h / 2
|
|
27708
|
+
});
|
|
27709
|
+
y += n.h + o.nodesep;
|
|
27710
|
+
let kids = t.children.get(id) || [];
|
|
27711
|
+
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
|
|
27712
|
+
}
|
|
27684
27713
|
};
|
|
27685
27714
|
for (let root of t.roots) {
|
|
27686
27715
|
visit(root);
|
|
@@ -62107,6 +62136,8 @@ function ac_set_operation_mode(gobj, event, kw, src) {
|
|
|
62107
62136
|
* into the URL, so what you are looking at stays linkable.
|
|
62108
62137
|
************************************************************/
|
|
62109
62138
|
var LEGEND_GLYPH_STYLE = "font-size:1.5em; line-height:1; color:inherit;";
|
|
62139
|
+
var LEGEND_NAME_STYLE = "font-size:1.2em; line-height:1.2;";
|
|
62140
|
+
var LEGEND_COUNT_STYLE = "margin-left:.4rem; font-size:1em;";
|
|
62110
62141
|
function refresh_legend(gobj) {
|
|
62111
62142
|
let priv = gobj.priv;
|
|
62112
62143
|
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
@@ -62132,19 +62163,19 @@ function refresh_legend(gobj) {
|
|
|
62132
62163
|
"span",
|
|
62133
62164
|
{
|
|
62134
62165
|
class: "GRAPH_LEGEND_NAME",
|
|
62135
|
-
style: hidden ? "text-decoration:line-through; opacity:.6;" : ""
|
|
62166
|
+
style: LEGEND_NAME_STYLE + (hidden ? " text-decoration:line-through; opacity:.6;" : "")
|
|
62136
62167
|
},
|
|
62137
62168
|
topic_name
|
|
62138
62169
|
], [
|
|
62139
62170
|
"span",
|
|
62140
62171
|
{
|
|
62141
62172
|
class: "GRAPH_LEGEND_COUNT has-text-grey",
|
|
62142
|
-
style:
|
|
62173
|
+
style: LEGEND_COUNT_STYLE
|
|
62143
62174
|
},
|
|
62144
62175
|
hidden ? `${entry.total}` : `${entry.visible}/${entry.total}`
|
|
62145
62176
|
]);
|
|
62146
62177
|
let body_attrs = {
|
|
62147
|
-
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? "
|
|
62178
|
+
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " pressed_state" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
|
|
62148
62179
|
type: "button",
|
|
62149
62180
|
style: "gap:0; flex:0 0 auto;",
|
|
62150
62181
|
title: is_main ? (0, i18next.t)("main topic") : hidden ? (0, i18next.t)("show topic") : (0, i18next.t)("hide topic"),
|
|
@@ -62168,7 +62199,7 @@ function refresh_legend(gobj) {
|
|
|
62168
62199
|
if (is_main && state.main_chosen) extras.push([
|
|
62169
62200
|
"button",
|
|
62170
62201
|
{
|
|
62171
|
-
class: "GRAPH_LEGEND_STAR button is-small
|
|
62202
|
+
class: "GRAPH_LEGEND_STAR button is-small pressed_state",
|
|
62172
62203
|
type: "button",
|
|
62173
62204
|
style: "padding:0 .4rem;",
|
|
62174
62205
|
title: (0, i18next.t)("main topic"),
|
|
@@ -62229,7 +62260,7 @@ function refresh_legend(gobj) {
|
|
|
62229
62260
|
if (!hidden && entry.linked && entry.loose > 0) extras.push([
|
|
62230
62261
|
"button",
|
|
62231
62262
|
{
|
|
62232
|
-
class: "GRAPH_LEGEND_LOOSE button is-small" + (entry.loose_shown ? "
|
|
62263
|
+
class: "GRAPH_LEGEND_LOOSE button is-small" + (entry.loose_shown ? " pressed_state" : ""),
|
|
62233
62264
|
type: "button",
|
|
62234
62265
|
style: "padding:0 .45rem;",
|
|
62235
62266
|
title: (0, i18next.t)("loose records"),
|
|
@@ -62249,7 +62280,7 @@ function refresh_legend(gobj) {
|
|
|
62249
62280
|
if (!hidden) extras.push([
|
|
62250
62281
|
"button",
|
|
62251
62282
|
{
|
|
62252
|
-
class: "GRAPH_LEGEND_FOCUS button is-small" + (focused ? "
|
|
62283
|
+
class: "GRAPH_LEGEND_FOCUS button is-small" + (focused ? " pressed_state" : ""),
|
|
62253
62284
|
type: "button",
|
|
62254
62285
|
style: "padding:0 .4rem;",
|
|
62255
62286
|
title: (0, i18next.t)("highlight topic"),
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -27487,6 +27487,28 @@ function spanning_tree(nodes, edges) {
|
|
|
27487
27487
|
};
|
|
27488
27488
|
}
|
|
27489
27489
|
/************************************************************
|
|
27490
|
+
* The ids of a spanning tree in PARENT-BEFORE-CHILD order.
|
|
27491
|
+
*
|
|
27492
|
+
* Every pass below used to be a recursive walk, which is the natural
|
|
27493
|
+
* way to write it and the one that dies on a deep tree: a
|
|
27494
|
+
* self-referent hook (a place inside a place inside a place) is as
|
|
27495
|
+
* deep as the data says, and the stack is not. Read this list
|
|
27496
|
+
* forwards for a pre-order pass and BACKWARDS for a post-order one
|
|
27497
|
+
* -- a child always sits after its parent, so reversed it always
|
|
27498
|
+
* sits before it, which is the whole of what post-order was for.
|
|
27499
|
+
************************************************************/
|
|
27500
|
+
function walk_order(t) {
|
|
27501
|
+
let order = [];
|
|
27502
|
+
let stack = t.roots.slice().reverse();
|
|
27503
|
+
while (stack.length) {
|
|
27504
|
+
let id = stack.pop();
|
|
27505
|
+
order.push(id);
|
|
27506
|
+
let kids = t.children.get(id) || [];
|
|
27507
|
+
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
|
|
27508
|
+
}
|
|
27509
|
+
return order;
|
|
27510
|
+
}
|
|
27511
|
+
/************************************************************
|
|
27490
27512
|
* The tidy tree, left to right.
|
|
27491
27513
|
*
|
|
27492
27514
|
* Returns Map<id, {x, y}> with the CENTRE of each node, the way G6
|
|
@@ -27507,56 +27529,45 @@ function layout_tree(nodes, edges, opts) {
|
|
|
27507
27529
|
col_x[d] = x;
|
|
27508
27530
|
x += (col_w[d] || 0) + o.ranksep;
|
|
27509
27531
|
}
|
|
27532
|
+
let order = walk_order(t);
|
|
27510
27533
|
let height = /* @__PURE__ */ new Map();
|
|
27511
|
-
let
|
|
27534
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
27535
|
+
let id = order[i];
|
|
27512
27536
|
let kids = t.children.get(id) || [];
|
|
27513
27537
|
let own = t.by_id.get(id).h;
|
|
27514
|
-
if (!kids.length) {
|
|
27515
|
-
height.set(id, own);
|
|
27516
|
-
return own;
|
|
27517
|
-
}
|
|
27518
27538
|
let sum = 0;
|
|
27519
|
-
for (let
|
|
27520
|
-
sum +=
|
|
27521
|
-
if (
|
|
27539
|
+
for (let k = 0; k < kids.length; k++) {
|
|
27540
|
+
sum += height.get(kids[k]);
|
|
27541
|
+
if (k > 0) sum += o.nodesep;
|
|
27522
27542
|
}
|
|
27523
|
-
|
|
27524
|
-
|
|
27525
|
-
|
|
27526
|
-
|
|
27527
|
-
for (let root of t.roots)
|
|
27528
|
-
|
|
27529
|
-
|
|
27543
|
+
height.set(id, kids.length ? Math.max(own, sum) : own);
|
|
27544
|
+
}
|
|
27545
|
+
let tops = /* @__PURE__ */ new Map();
|
|
27546
|
+
let top = 0;
|
|
27547
|
+
for (let root of t.roots) {
|
|
27548
|
+
tops.set(root, top);
|
|
27549
|
+
top += height.get(root) + o.nodesep * 2;
|
|
27550
|
+
}
|
|
27551
|
+
for (let id of order) {
|
|
27530
27552
|
let d = t.depth.get(id);
|
|
27531
27553
|
let kids = t.children.get(id) || [];
|
|
27532
27554
|
let block = height.get(id);
|
|
27533
|
-
let
|
|
27534
|
-
|
|
27535
|
-
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
return;
|
|
27540
|
-
}
|
|
27555
|
+
let own_top = tops.get(id);
|
|
27556
|
+
pos.set(id, {
|
|
27557
|
+
x: col_x[d] + col_w[d] / 2,
|
|
27558
|
+
y: own_top + block / 2
|
|
27559
|
+
});
|
|
27560
|
+
if (!kids.length) continue;
|
|
27541
27561
|
let kids_h = 0;
|
|
27542
|
-
for (let
|
|
27543
|
-
kids_h += height.get(kids[
|
|
27544
|
-
if (
|
|
27562
|
+
for (let k = 0; k < kids.length; k++) {
|
|
27563
|
+
kids_h += height.get(kids[k]);
|
|
27564
|
+
if (k > 0) kids_h += o.nodesep;
|
|
27545
27565
|
}
|
|
27546
|
-
let cursor =
|
|
27566
|
+
let cursor = own_top + (block - kids_h) / 2;
|
|
27547
27567
|
for (let kid of kids) {
|
|
27548
|
-
|
|
27568
|
+
tops.set(kid, cursor);
|
|
27549
27569
|
cursor += height.get(kid) + o.nodesep;
|
|
27550
27570
|
}
|
|
27551
|
-
pos.set(id, {
|
|
27552
|
-
x: cx,
|
|
27553
|
-
y: top + block / 2
|
|
27554
|
-
});
|
|
27555
|
-
};
|
|
27556
|
-
let top = 0;
|
|
27557
|
-
for (let root of t.roots) {
|
|
27558
|
-
place(root, top);
|
|
27559
|
-
top += height.get(root) + o.nodesep * 2;
|
|
27560
27571
|
}
|
|
27561
27572
|
return pos;
|
|
27562
27573
|
}
|
|
@@ -27583,40 +27594,53 @@ function layout_radial(nodes, edges, opts) {
|
|
|
27583
27594
|
let t = spanning_tree(nodes, edges);
|
|
27584
27595
|
let pos = /* @__PURE__ */ new Map();
|
|
27585
27596
|
if (!t.roots.length) return pos;
|
|
27597
|
+
let order = walk_order(t);
|
|
27586
27598
|
let leaves = /* @__PURE__ */ new Map();
|
|
27587
|
-
let
|
|
27599
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
27600
|
+
let id = order[i];
|
|
27588
27601
|
let kids = t.children.get(id) || [];
|
|
27589
27602
|
if (!kids.length) {
|
|
27590
27603
|
leaves.set(id, 1);
|
|
27591
|
-
|
|
27604
|
+
continue;
|
|
27592
27605
|
}
|
|
27593
27606
|
let n = 0;
|
|
27594
|
-
for (let kid of kids) n +=
|
|
27607
|
+
for (let kid of kids) n += leaves.get(kid);
|
|
27595
27608
|
leaves.set(id, n);
|
|
27596
|
-
|
|
27597
|
-
};
|
|
27609
|
+
}
|
|
27598
27610
|
let total = 0;
|
|
27599
|
-
for (let root of t.roots) total +=
|
|
27611
|
+
for (let root of t.roots) total += leaves.get(root);
|
|
27600
27612
|
let single = t.roots.length === 1;
|
|
27601
27613
|
let ring_of = (id) => t.depth.get(id) + (single ? 0 : 1);
|
|
27602
27614
|
let angle = /* @__PURE__ */ new Map();
|
|
27603
27615
|
let rings = [];
|
|
27604
|
-
let sector = (
|
|
27605
|
-
let
|
|
27606
|
-
|
|
27607
|
-
|
|
27608
|
-
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
id,
|
|
27612
|
-
|
|
27613
|
-
|
|
27614
|
-
|
|
27615
|
-
|
|
27616
|
-
|
|
27617
|
-
|
|
27618
|
-
|
|
27619
|
-
|
|
27616
|
+
let sector = (root_id, root_from, root_span) => {
|
|
27617
|
+
let stack = [{
|
|
27618
|
+
id: root_id,
|
|
27619
|
+
from: root_from,
|
|
27620
|
+
span: root_span
|
|
27621
|
+
}];
|
|
27622
|
+
while (stack.length) {
|
|
27623
|
+
let { id, from, span } = stack.pop();
|
|
27624
|
+
let a = from + span / 2;
|
|
27625
|
+
angle.set(id, a);
|
|
27626
|
+
let ring = ring_of(id);
|
|
27627
|
+
if (!rings[ring]) rings[ring] = [];
|
|
27628
|
+
let n = t.by_id.get(id);
|
|
27629
|
+
rings[ring].push({
|
|
27630
|
+
id,
|
|
27631
|
+
angle: a,
|
|
27632
|
+
diag: Math.hypot(n.w, n.h)
|
|
27633
|
+
});
|
|
27634
|
+
let cursor = from;
|
|
27635
|
+
for (let kid of t.children.get(id) || []) {
|
|
27636
|
+
let part = span * leaves.get(kid) / leaves.get(id);
|
|
27637
|
+
stack.push({
|
|
27638
|
+
id: kid,
|
|
27639
|
+
from: cursor,
|
|
27640
|
+
span: part
|
|
27641
|
+
});
|
|
27642
|
+
cursor += part;
|
|
27643
|
+
}
|
|
27620
27644
|
}
|
|
27621
27645
|
};
|
|
27622
27646
|
if (single) {
|
|
@@ -27667,15 +27691,20 @@ function layout_outline(nodes, edges, opts) {
|
|
|
27667
27691
|
let t = spanning_tree(nodes, edges);
|
|
27668
27692
|
let pos = /* @__PURE__ */ new Map();
|
|
27669
27693
|
let y = 0;
|
|
27670
|
-
let visit = (
|
|
27671
|
-
let
|
|
27672
|
-
|
|
27673
|
-
|
|
27674
|
-
|
|
27675
|
-
|
|
27676
|
-
|
|
27677
|
-
|
|
27678
|
-
|
|
27694
|
+
let visit = (root_id) => {
|
|
27695
|
+
let stack = [root_id];
|
|
27696
|
+
while (stack.length) {
|
|
27697
|
+
let id = stack.pop();
|
|
27698
|
+
let n = t.by_id.get(id);
|
|
27699
|
+
let d = t.depth.get(id);
|
|
27700
|
+
pos.set(id, {
|
|
27701
|
+
x: d * o.indent + n.w / 2,
|
|
27702
|
+
y: y + n.h / 2
|
|
27703
|
+
});
|
|
27704
|
+
y += n.h + o.nodesep;
|
|
27705
|
+
let kids = t.children.get(id) || [];
|
|
27706
|
+
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
|
|
27707
|
+
}
|
|
27679
27708
|
};
|
|
27680
27709
|
for (let root of t.roots) {
|
|
27681
27710
|
visit(root);
|
|
@@ -62102,6 +62131,8 @@ function ac_set_operation_mode(gobj, event, kw, src) {
|
|
|
62102
62131
|
* into the URL, so what you are looking at stays linkable.
|
|
62103
62132
|
************************************************************/
|
|
62104
62133
|
var LEGEND_GLYPH_STYLE = "font-size:1.5em; line-height:1; color:inherit;";
|
|
62134
|
+
var LEGEND_NAME_STYLE = "font-size:1.2em; line-height:1.2;";
|
|
62135
|
+
var LEGEND_COUNT_STYLE = "margin-left:.4rem; font-size:1em;";
|
|
62105
62136
|
function refresh_legend(gobj) {
|
|
62106
62137
|
let priv = gobj.priv;
|
|
62107
62138
|
let $container = gobj_read_attr(gobj, "$container");
|
|
@@ -62127,19 +62158,19 @@ function refresh_legend(gobj) {
|
|
|
62127
62158
|
"span",
|
|
62128
62159
|
{
|
|
62129
62160
|
class: "GRAPH_LEGEND_NAME",
|
|
62130
|
-
style: hidden ? "text-decoration:line-through; opacity:.6;" : ""
|
|
62161
|
+
style: LEGEND_NAME_STYLE + (hidden ? " text-decoration:line-through; opacity:.6;" : "")
|
|
62131
62162
|
},
|
|
62132
62163
|
topic_name
|
|
62133
62164
|
], [
|
|
62134
62165
|
"span",
|
|
62135
62166
|
{
|
|
62136
62167
|
class: "GRAPH_LEGEND_COUNT has-text-grey",
|
|
62137
|
-
style:
|
|
62168
|
+
style: LEGEND_COUNT_STYLE
|
|
62138
62169
|
},
|
|
62139
62170
|
hidden ? `${entry.total}` : `${entry.visible}/${entry.total}`
|
|
62140
62171
|
]);
|
|
62141
62172
|
let body_attrs = {
|
|
62142
|
-
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? "
|
|
62173
|
+
class: "GRAPH_LEGEND_ITEM button is-small" + (focused ? " pressed_state" : "") + (is_main ? " GRAPH_LEGEND_MAIN" : ""),
|
|
62143
62174
|
type: "button",
|
|
62144
62175
|
style: "gap:0; flex:0 0 auto;",
|
|
62145
62176
|
title: is_main ? t("main topic") : hidden ? t("show topic") : t("hide topic"),
|
|
@@ -62163,7 +62194,7 @@ function refresh_legend(gobj) {
|
|
|
62163
62194
|
if (is_main && state.main_chosen) extras.push([
|
|
62164
62195
|
"button",
|
|
62165
62196
|
{
|
|
62166
|
-
class: "GRAPH_LEGEND_STAR button is-small
|
|
62197
|
+
class: "GRAPH_LEGEND_STAR button is-small pressed_state",
|
|
62167
62198
|
type: "button",
|
|
62168
62199
|
style: "padding:0 .4rem;",
|
|
62169
62200
|
title: t("main topic"),
|
|
@@ -62224,7 +62255,7 @@ function refresh_legend(gobj) {
|
|
|
62224
62255
|
if (!hidden && entry.linked && entry.loose > 0) extras.push([
|
|
62225
62256
|
"button",
|
|
62226
62257
|
{
|
|
62227
|
-
class: "GRAPH_LEGEND_LOOSE button is-small" + (entry.loose_shown ? "
|
|
62258
|
+
class: "GRAPH_LEGEND_LOOSE button is-small" + (entry.loose_shown ? " pressed_state" : ""),
|
|
62228
62259
|
type: "button",
|
|
62229
62260
|
style: "padding:0 .45rem;",
|
|
62230
62261
|
title: t("loose records"),
|
|
@@ -62244,7 +62275,7 @@ function refresh_legend(gobj) {
|
|
|
62244
62275
|
if (!hidden) extras.push([
|
|
62245
62276
|
"button",
|
|
62246
62277
|
{
|
|
62247
|
-
class: "GRAPH_LEGEND_FOCUS button is-small" + (focused ? "
|
|
62278
|
+
class: "GRAPH_LEGEND_FOCUS button is-small" + (focused ? " pressed_state" : ""),
|
|
62248
62279
|
type: "button",
|
|
62249
62280
|
style: "padding:0 .4rem;",
|
|
62250
62281
|
title: t("highlight topic"),
|
package/package.json
CHANGED
|
@@ -2339,6 +2339,15 @@ function ac_set_operation_mode(gobj, event, kw, src)
|
|
|
2339
2339
|
* stays small so the strip keeps its height; the glyph inside grows. */
|
|
2340
2340
|
const LEGEND_GLYPH_STYLE = 'font-size:1.5em; line-height:1; color:inherit;';
|
|
2341
2341
|
|
|
2342
|
+
/* And the same for what a chip SAYS. The legend is a layer control --
|
|
2343
|
+
* a gadget somebody uses, not a footnote somebody glances at -- and a
|
|
2344
|
+
* topic name is the thing it is read for, so 0.75rem is the wrong
|
|
2345
|
+
* size for it. The button stays `is-small` because the strip has to
|
|
2346
|
+
* hold a chip per topic on one row; the name and the count come back
|
|
2347
|
+
* up inside it, exactly as the glyphs did. */
|
|
2348
|
+
const LEGEND_NAME_STYLE = 'font-size:1.2em; line-height:1.2;';
|
|
2349
|
+
const LEGEND_COUNT_STYLE = 'margin-left:.4rem; font-size:1em;';
|
|
2350
|
+
|
|
2342
2351
|
function refresh_legend(gobj)
|
|
2343
2352
|
{
|
|
2344
2353
|
let priv = gobj.priv;
|
|
@@ -2389,10 +2398,11 @@ function refresh_legend(gobj)
|
|
|
2389
2398
|
`border:1px solid rgba(0,0,0,.25);` +
|
|
2390
2399
|
(hidden? 'opacity:.35;' : '')}],
|
|
2391
2400
|
['span', {class: 'GRAPH_LEGEND_NAME',
|
|
2392
|
-
style:
|
|
2401
|
+
style: LEGEND_NAME_STYLE +
|
|
2402
|
+
(hidden? ' text-decoration:line-through; opacity:.6;' : '')},
|
|
2393
2403
|
topic_name],
|
|
2394
2404
|
['span', {class: 'GRAPH_LEGEND_COUNT has-text-grey',
|
|
2395
|
-
style:
|
|
2405
|
+
style: LEGEND_COUNT_STYLE},
|
|
2396
2406
|
hidden? `${entry.total}` : `${entry.visible}/${entry.total}`]
|
|
2397
2407
|
);
|
|
2398
2408
|
|
|
@@ -2402,7 +2412,7 @@ function refresh_legend(gobj)
|
|
|
2402
2412
|
* every chip was dead except its star. */
|
|
2403
2413
|
let body_attrs = {
|
|
2404
2414
|
class: 'GRAPH_LEGEND_ITEM button is-small' +
|
|
2405
|
-
(focused? '
|
|
2415
|
+
(focused? ' pressed_state' : '') +
|
|
2406
2416
|
(is_main? ' GRAPH_LEGEND_MAIN' : ''),
|
|
2407
2417
|
type: 'button',
|
|
2408
2418
|
style: 'gap:0; flex:0 0 auto;',
|
|
@@ -2430,7 +2440,7 @@ function refresh_legend(gobj)
|
|
|
2430
2440
|
* is a button too: pressing it hands the choice back to the
|
|
2431
2441
|
* graph (deduced again). On a deduced one it is a mark. */
|
|
2432
2442
|
if(is_main && state.main_chosen) {
|
|
2433
|
-
extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small
|
|
2443
|
+
extras.push(['button', {class: 'GRAPH_LEGEND_STAR button is-small pressed_state',
|
|
2434
2444
|
type: 'button', style: 'padding:0 .4rem;',
|
|
2435
2445
|
title: t('main topic'), 'data-i18n-title': 'main topic',
|
|
2436
2446
|
'aria-label': t('main topic'), 'aria-pressed': 'true'},
|
|
@@ -2466,7 +2476,7 @@ function refresh_legend(gobj)
|
|
|
2466
2476
|
if(!hidden && entry.linked && entry.loose > 0) {
|
|
2467
2477
|
extras.push(['button', {
|
|
2468
2478
|
class: 'GRAPH_LEGEND_LOOSE button is-small' +
|
|
2469
|
-
(entry.loose_shown? '
|
|
2479
|
+
(entry.loose_shown? ' pressed_state' : ''),
|
|
2470
2480
|
type: 'button', style: 'padding:0 .45rem;',
|
|
2471
2481
|
title: t('loose records'), 'data-i18n-title': 'loose records',
|
|
2472
2482
|
'aria-label': t('loose records'),
|
|
@@ -2483,7 +2493,7 @@ function refresh_legend(gobj)
|
|
|
2483
2493
|
if(!hidden) {
|
|
2484
2494
|
extras.push(['button', {
|
|
2485
2495
|
class: 'GRAPH_LEGEND_FOCUS button is-small' +
|
|
2486
|
-
(focused? '
|
|
2496
|
+
(focused? ' pressed_state' : ''),
|
|
2487
2497
|
type: 'button', style: 'padding:0 .4rem;',
|
|
2488
2498
|
title: t('highlight topic'), 'data-i18n-title': 'highlight topic',
|
|
2489
2499
|
'aria-label': t('highlight topic'),
|
package/src/treedb_layout.js
CHANGED
|
@@ -142,6 +142,32 @@ export function spanning_tree(nodes, edges)
|
|
|
142
142
|
return {roots: roots, children: children, depth: depth, by_id: by_id};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/************************************************************
|
|
146
|
+
* The ids of a spanning tree in PARENT-BEFORE-CHILD order.
|
|
147
|
+
*
|
|
148
|
+
* Every pass below used to be a recursive walk, which is the natural
|
|
149
|
+
* way to write it and the one that dies on a deep tree: a
|
|
150
|
+
* self-referent hook (a place inside a place inside a place) is as
|
|
151
|
+
* deep as the data says, and the stack is not. Read this list
|
|
152
|
+
* forwards for a pre-order pass and BACKWARDS for a post-order one
|
|
153
|
+
* -- a child always sits after its parent, so reversed it always
|
|
154
|
+
* sits before it, which is the whole of what post-order was for.
|
|
155
|
+
************************************************************/
|
|
156
|
+
function walk_order(t)
|
|
157
|
+
{
|
|
158
|
+
let order = [];
|
|
159
|
+
let stack = t.roots.slice().reverse();
|
|
160
|
+
while(stack.length) {
|
|
161
|
+
let id = stack.pop();
|
|
162
|
+
order.push(id);
|
|
163
|
+
let kids = t.children.get(id) || [];
|
|
164
|
+
for(let i = kids.length - 1; i >= 0; i--) {
|
|
165
|
+
stack.push(kids[i]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return order;
|
|
169
|
+
}
|
|
170
|
+
|
|
145
171
|
/************************************************************
|
|
146
172
|
* The tidy tree, left to right.
|
|
147
173
|
*
|
|
@@ -167,60 +193,54 @@ export function layout_tree(nodes, edges, opts)
|
|
|
167
193
|
x += (col_w[d] || 0) + o.ranksep;
|
|
168
194
|
}
|
|
169
195
|
|
|
170
|
-
/* Subtree heights
|
|
196
|
+
/* Subtree heights: post-order, so children before parents. */
|
|
197
|
+
let order = walk_order(t);
|
|
171
198
|
let height = new Map();
|
|
172
|
-
let
|
|
199
|
+
for(let i = order.length - 1; i >= 0; i--) {
|
|
200
|
+
let id = order[i];
|
|
173
201
|
let kids = t.children.get(id) || [];
|
|
174
202
|
let own = t.by_id.get(id).h;
|
|
175
|
-
if(!kids.length) {
|
|
176
|
-
height.set(id, own);
|
|
177
|
-
return own;
|
|
178
|
-
}
|
|
179
203
|
let sum = 0;
|
|
180
|
-
for(let
|
|
181
|
-
sum +=
|
|
182
|
-
if(
|
|
204
|
+
for(let k = 0; k < kids.length; k++) {
|
|
205
|
+
sum += height.get(kids[k]);
|
|
206
|
+
if(k > 0) {
|
|
183
207
|
sum += o.nodesep;
|
|
184
208
|
}
|
|
185
209
|
}
|
|
186
|
-
|
|
187
|
-
height.set(id, h);
|
|
188
|
-
return h;
|
|
189
|
-
};
|
|
190
|
-
for(let root of t.roots) {
|
|
191
|
-
measure(root);
|
|
210
|
+
height.set(id, kids.length? Math.max(own, sum) : own);
|
|
192
211
|
}
|
|
193
212
|
|
|
194
213
|
/* Placement, pre-order: a node is centred on the block of its
|
|
195
|
-
* children, which starts at the top of its own block.
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
* children, which starts at the top of its own block. Every node
|
|
215
|
+
* in a column is centred on the column, whatever its own width --
|
|
216
|
+
* the cards of one depth line up by their middles. */
|
|
217
|
+
let tops = new Map();
|
|
218
|
+
let top = 0;
|
|
219
|
+
for(let root of t.roots) {
|
|
220
|
+
tops.set(root, top);
|
|
221
|
+
top += height.get(root) + o.nodesep * 2;
|
|
222
|
+
}
|
|
223
|
+
for(let id of order) {
|
|
198
224
|
let d = t.depth.get(id);
|
|
199
225
|
let kids = t.children.get(id) || [];
|
|
200
226
|
let block = height.get(id);
|
|
201
|
-
let
|
|
227
|
+
let own_top = tops.get(id);
|
|
228
|
+
pos.set(id, {x: col_x[d] + col_w[d] / 2, y: own_top + block / 2});
|
|
202
229
|
if(!kids.length) {
|
|
203
|
-
|
|
204
|
-
return;
|
|
230
|
+
continue;
|
|
205
231
|
}
|
|
206
232
|
let kids_h = 0;
|
|
207
|
-
for(let
|
|
208
|
-
kids_h += height.get(kids[
|
|
209
|
-
if(
|
|
233
|
+
for(let k = 0; k < kids.length; k++) {
|
|
234
|
+
kids_h += height.get(kids[k]);
|
|
235
|
+
if(k > 0) {
|
|
210
236
|
kids_h += o.nodesep;
|
|
211
237
|
}
|
|
212
238
|
}
|
|
213
|
-
let cursor =
|
|
239
|
+
let cursor = own_top + (block - kids_h) / 2;
|
|
214
240
|
for(let kid of kids) {
|
|
215
|
-
|
|
241
|
+
tops.set(kid, cursor);
|
|
216
242
|
cursor += height.get(kid) + o.nodesep;
|
|
217
243
|
}
|
|
218
|
-
pos.set(id, {x: cx, y: top + block / 2});
|
|
219
|
-
};
|
|
220
|
-
let top = 0;
|
|
221
|
-
for(let root of t.roots) {
|
|
222
|
-
place(root, top);
|
|
223
|
-
top += height.get(root) + o.nodesep * 2;
|
|
224
244
|
}
|
|
225
245
|
|
|
226
246
|
return pos;
|
|
@@ -253,24 +273,25 @@ export function layout_radial(nodes, edges, opts)
|
|
|
253
273
|
return pos;
|
|
254
274
|
}
|
|
255
275
|
|
|
256
|
-
/* Leaves under each node: the sector it deserves. */
|
|
276
|
+
/* Leaves under each node: the sector it deserves. Post-order. */
|
|
277
|
+
let order = walk_order(t);
|
|
257
278
|
let leaves = new Map();
|
|
258
|
-
let
|
|
279
|
+
for(let i = order.length - 1; i >= 0; i--) {
|
|
280
|
+
let id = order[i];
|
|
259
281
|
let kids = t.children.get(id) || [];
|
|
260
282
|
if(!kids.length) {
|
|
261
283
|
leaves.set(id, 1);
|
|
262
|
-
|
|
284
|
+
continue;
|
|
263
285
|
}
|
|
264
286
|
let n = 0;
|
|
265
287
|
for(let kid of kids) {
|
|
266
|
-
n +=
|
|
288
|
+
n += leaves.get(kid);
|
|
267
289
|
}
|
|
268
290
|
leaves.set(id, n);
|
|
269
|
-
|
|
270
|
-
};
|
|
291
|
+
}
|
|
271
292
|
let total = 0;
|
|
272
293
|
for(let root of t.roots) {
|
|
273
|
-
total +=
|
|
294
|
+
total += leaves.get(root);
|
|
274
295
|
}
|
|
275
296
|
|
|
276
297
|
/* One root sits at the centre; several sit on the first ring
|
|
@@ -283,20 +304,27 @@ export function layout_radial(nodes, edges, opts)
|
|
|
283
304
|
* Angles do not depend on the radii, so they come first. */
|
|
284
305
|
let angle = new Map();
|
|
285
306
|
let rings = []; /* ring -> [{id, angle, diag}] */
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
/* An explicit stack, not recursion: the sectors of a ring are
|
|
308
|
+
* sorted by angle in pass 2, so the order they are pushed in does
|
|
309
|
+
* not matter -- only that every node gets its own. */
|
|
310
|
+
let sector = (root_id, root_from, root_span) => {
|
|
311
|
+
let stack = [{id: root_id, from: root_from, span: root_span}];
|
|
312
|
+
while(stack.length) {
|
|
313
|
+
let {id, from, span} = stack.pop();
|
|
314
|
+
let a = from + span / 2;
|
|
315
|
+
angle.set(id, a);
|
|
316
|
+
let ring = ring_of(id);
|
|
317
|
+
if(!rings[ring]) {
|
|
318
|
+
rings[ring] = [];
|
|
319
|
+
}
|
|
320
|
+
let n = t.by_id.get(id);
|
|
321
|
+
rings[ring].push({id: id, angle: a, diag: Math.hypot(n.w, n.h)});
|
|
322
|
+
let cursor = from;
|
|
323
|
+
for(let kid of (t.children.get(id) || [])) {
|
|
324
|
+
let part = span * leaves.get(kid) / leaves.get(id);
|
|
325
|
+
stack.push({id: kid, from: cursor, span: part});
|
|
326
|
+
cursor += part;
|
|
327
|
+
}
|
|
300
328
|
}
|
|
301
329
|
};
|
|
302
330
|
if(single) {
|
|
@@ -362,13 +390,20 @@ export function layout_outline(nodes, edges, opts)
|
|
|
362
390
|
let pos = new Map();
|
|
363
391
|
|
|
364
392
|
let y = 0;
|
|
365
|
-
let visit = (
|
|
366
|
-
let
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
393
|
+
let visit = (root_id) => {
|
|
394
|
+
let stack = [root_id];
|
|
395
|
+
while(stack.length) {
|
|
396
|
+
let id = stack.pop();
|
|
397
|
+
let n = t.by_id.get(id);
|
|
398
|
+
let d = t.depth.get(id);
|
|
399
|
+
pos.set(id, {x: d * o.indent + n.w / 2, y: y + n.h / 2});
|
|
400
|
+
y += n.h + o.nodesep;
|
|
401
|
+
/* Reversed, so a sibling pops in the order it was given:
|
|
402
|
+
* here the order of the walk IS the order of the rows. */
|
|
403
|
+
let kids = t.children.get(id) || [];
|
|
404
|
+
for(let i = kids.length - 1; i >= 0; i--) {
|
|
405
|
+
stack.push(kids[i]);
|
|
406
|
+
}
|
|
372
407
|
}
|
|
373
408
|
};
|
|
374
409
|
for(let root of t.roots) {
|
|
@@ -170,3 +170,35 @@ describe("the outline", () => {
|
|
|
170
170
|
expect(pos.get("norte").y).toBe(96 + 10 + 48);
|
|
171
171
|
});
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
describe("a deep tree", () => {
|
|
175
|
+
/* A self-referent hook -- a place inside a place inside a place --
|
|
176
|
+
* is as deep as the data says. The walks are iterative for exactly
|
|
177
|
+
* this: recursive ones died here with "Maximum call stack size
|
|
178
|
+
* exceeded", and a graph that cannot be drawn is not a layout
|
|
179
|
+
* choice, it is an exception in the console. */
|
|
180
|
+
const DEEP = 20000;
|
|
181
|
+
const deep_nodes = [];
|
|
182
|
+
const deep_edges = [];
|
|
183
|
+
for(let i = 0; i < DEEP; i++) {
|
|
184
|
+
deep_nodes.push({id: `n${i}`, w: 100, h: 40});
|
|
185
|
+
if(i > 0) {
|
|
186
|
+
deep_edges.push({source: `n${i - 1}`, target: `n${i}`, rank: 0});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test("the spanning tree reaches the bottom", () => {
|
|
191
|
+
let t = spanning_tree(deep_nodes, deep_edges);
|
|
192
|
+
expect(t.roots).toEqual(["n0"]);
|
|
193
|
+
expect(t.depth.get(`n${DEEP - 1}`)).toBe(DEEP - 1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("the three layouts place every node of it", () => {
|
|
197
|
+
for(let layout of [layout_tree, layout_outline, layout_radial]) {
|
|
198
|
+
let pos = layout(deep_nodes, deep_edges);
|
|
199
|
+
expect(pos.size).toBe(DEEP);
|
|
200
|
+
expect(Number.isFinite(pos.get(`n${DEEP - 1}`).x)).toBe(true);
|
|
201
|
+
expect(Number.isFinite(pos.get(`n${DEEP - 1}`).y)).toBe(true);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|