@euphrasiologist/lwphylo 1.1.7 → 1.1.9
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/dist/index.js +2 -0
- package/package.json +9 -5
- package/src/phisheye/phisheye.js +111 -65
- package/src/radial/describeArc.js +22 -12
- package/src/radial/getArcs.js +47 -56
- package/src/radial/getRadii.js +32 -38
- package/src/radial/polarToCartesian.js +5 -7
- package/src/radial/radialData.js +71 -89
- package/src/radial/radialLayout.js +8 -9
- package/src/radial/reflectAngle.js +7 -17
- package/src/rectangle/getChildVerticals.js +47 -0
- package/src/rectangle/getHorizontal.js +13 -7
- package/src/rectangle/getVertical.js +2 -1
- package/src/rectangle/rectangleLayout.js +23 -10
- package/src/unrooted/equalAngleLayout.js +36 -25
- package/src/unrooted/unrooted.js +1 -1
- package/src/utils/edges.js +13 -42
- package/src/utils/indexing.js +64 -0
- package/src/utils/meanAngle.js +10 -35
- package/src/utils/numTips.js +15 -20
- package/src/utils/parentFisheye.js +9 -11
- package/src/utils/preorder.js +33 -6
- package/src/utils/readTree.js +53 -63
- package/dist/@euphrasiologist/lwphylo.js +0 -822
- package/dist/@euphrasiologist/lwphylo.min.js +0 -2
|
@@ -3,17 +3,16 @@ import getRadii from "./getRadii.js"
|
|
|
3
3
|
import getArcs from "./getArcs.js"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Simple wrapper
|
|
7
|
-
*
|
|
6
|
+
* Simple wrapper for radial layout:
|
|
7
|
+
* - data: per-node { angle, r, x, y, ... }
|
|
8
|
+
* - radii: per-edge radial spokes (parent.r → child.r)
|
|
9
|
+
* - arcs: per-internal-node arcs spanning its children at parent radius
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var data = {};
|
|
12
|
-
|
|
13
|
-
// TODO: does radial_data need to be printed out?
|
|
11
|
+
export default function radialLayout(node) {
|
|
12
|
+
const data = {};
|
|
14
13
|
data.data = radialData(node);
|
|
15
14
|
data.radii = getRadii(node);
|
|
16
15
|
data.arcs = getArcs(data.data);
|
|
17
|
-
|
|
18
16
|
return data;
|
|
19
|
-
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* thanks https://codereview.stackexchange.com/questions/187510/angle-reflection-function
|
|
2
|
+
* Legacy shim: normalize angle to [0, 2π).
|
|
3
|
+
* The new radial code does not need geometric reflections;
|
|
4
|
+
* it uses circular spans and atan2.
|
|
6
5
|
*/
|
|
6
|
+
export default function reflectAngle(rad /*, dir */) {
|
|
7
|
+
const TAU = Math.PI * 2;
|
|
8
|
+
return ((rad % TAU) + TAU) % TAU;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
|
-
export default function (rad, dir) {
|
|
9
|
-
const c = Math.cos(rad), s = Math.sin(rad);
|
|
10
|
-
const PI_sub = "3.1415";
|
|
11
|
-
|
|
12
|
-
function checkSign(x) {
|
|
13
|
-
if (x.toString().includes(PI_sub)) {
|
|
14
|
-
x = Math.abs(x);
|
|
15
|
-
}
|
|
16
|
-
return x;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return checkSign(Math.atan2(...(dir === "X" ? [s, -c] : [-s, c])));
|
|
20
|
-
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import getHorizontal from "./getHorizontal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build per-child vertical segments for a rectangular tree:
|
|
5
|
+
* For each non-root node (child), draw a vertical from (parent.x, child.y) to (parent.x, parent.y).
|
|
6
|
+
* This yields exactly one vertical per edge (child->parent), making highlighting trivial.
|
|
7
|
+
*
|
|
8
|
+
* Returns an array of:
|
|
9
|
+
* {
|
|
10
|
+
* parentId: number,
|
|
11
|
+
* childId: number,
|
|
12
|
+
* x: number, // x of the parent junction
|
|
13
|
+
* y0: number, // min(child.y, parent.y)
|
|
14
|
+
* y1: number, // max(child.y, parent.y)
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export default function getChildVerticals(node) {
|
|
18
|
+
const data = getHorizontal(node); // has parentId, thisId, x0,x1,y0=y1
|
|
19
|
+
|
|
20
|
+
// Build a quick index to access parent's y by id
|
|
21
|
+
const byId = new Map(data.map(d => [d.thisId, d]));
|
|
22
|
+
|
|
23
|
+
const childVerticals = [];
|
|
24
|
+
|
|
25
|
+
for (const d of data) {
|
|
26
|
+
if (d.parentId == null) continue;
|
|
27
|
+
const parent = byId.get(d.parentId);
|
|
28
|
+
if (!parent) continue;
|
|
29
|
+
|
|
30
|
+
const x = d.x0; // child’s vertical sits at parent.x == child.x0
|
|
31
|
+
const yc = d.y0; // child y
|
|
32
|
+
const yp = parent.y0; // parent y
|
|
33
|
+
const y0 = Math.min(yc, yp);
|
|
34
|
+
const y1 = Math.max(yc, yp);
|
|
35
|
+
|
|
36
|
+
childVerticals.push({
|
|
37
|
+
parentId: d.parentId,
|
|
38
|
+
childId: d.thisId,
|
|
39
|
+
x,
|
|
40
|
+
y0,
|
|
41
|
+
y1
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return childVerticals;
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -2,11 +2,13 @@ import mean from "../utils/mean.js"
|
|
|
2
2
|
import fortify from "../utils/fortify.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Rectangle layout
|
|
6
|
-
*
|
|
5
|
+
* Rectangle layout: compute per-node x0,x1 and y0=y1
|
|
6
|
+
* - Tip y is assigned by input order (preserves ladderize/order)
|
|
7
|
+
* - Internal node y is mean of child y's
|
|
8
|
+
* - x1 accumulates branch lengths from root
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
export default function(node) {
|
|
11
|
+
export default function getHorizontal(node) {
|
|
10
12
|
const pd = fortify(node);
|
|
11
13
|
|
|
12
14
|
// Fast lookup from id -> pd index
|
|
@@ -22,7 +24,7 @@ export default function(node) {
|
|
|
22
24
|
// Map each leaf id to a vertical slot (1..N)
|
|
23
25
|
const tipSlot = new Map(leafIds.map((id, i) => [id, i + 1]));
|
|
24
26
|
|
|
25
|
-
// 2) Set Y for tips directly from that order;
|
|
27
|
+
// 2) Set Y for tips directly from that order; internal node Y via children mean
|
|
26
28
|
(function setY(n) {
|
|
27
29
|
const i = idIndex.get(n.id);
|
|
28
30
|
if (!n.children || n.children.length === 0) {
|
|
@@ -36,7 +38,7 @@ export default function(node) {
|
|
|
36
38
|
return y;
|
|
37
39
|
})(node);
|
|
38
40
|
|
|
39
|
-
// 3) Set X by accumulating branch lengths down the tree
|
|
41
|
+
// 3) Set X by accumulating branch lengths down the tree
|
|
40
42
|
(function setX(n, xParent) {
|
|
41
43
|
const i = idIndex.get(n.id);
|
|
42
44
|
const bl = pd[i].branchLength ?? 0;
|
|
@@ -46,6 +48,10 @@ export default function(node) {
|
|
|
46
48
|
if (n.children && n.children.length) n.children.forEach(c => setX(c, x1));
|
|
47
49
|
})(node, 0);
|
|
48
50
|
|
|
49
|
-
// Clean up
|
|
50
|
-
return pd.map((
|
|
51
|
+
// Clean up: remove fields not needed downstream without triggering no-unused-vars
|
|
52
|
+
return pd.map((row) => {
|
|
53
|
+
const { y: _y, x: _x, angle: _angle, ...item } = row;
|
|
54
|
+
return item;
|
|
55
|
+
});
|
|
51
56
|
}
|
|
57
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import getHorizontal from "./getHorizontal.js"
|
|
2
2
|
|
|
3
|
-
export default function (node) {
|
|
3
|
+
export default function getVertical(node) {
|
|
4
4
|
const data = getHorizontal(node);
|
|
5
5
|
|
|
6
6
|
// Group rows by parentId (children that share a parent)
|
|
@@ -33,3 +33,4 @@ export default function (node) {
|
|
|
33
33
|
|
|
34
34
|
return verticals;
|
|
35
35
|
}
|
|
36
|
+
|
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
import getHorizontal from "./getHorizontal.js"
|
|
2
|
-
import getVertical from "./getVertical.js"
|
|
1
|
+
import getHorizontal from "./getHorizontal.js";
|
|
2
|
+
import getVertical from "./getVertical.js";
|
|
3
|
+
import getChildVerticals from "./getChildVerticals.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Rectangle layout wrapper.
|
|
7
|
+
* Returns:
|
|
8
|
+
* - data: per-node rows (x0,x1,y0=y1,...)
|
|
9
|
+
* - vertical_lines: single spanning vertical per parent (fast baseline draw)
|
|
10
|
+
* - child_vertical_lines: one vertical per edge (child->parent) for highlighting
|
|
11
|
+
* - horizontal_lines: child horizontals from parent.x -> child.x at child.y
|
|
6
12
|
*/
|
|
13
|
+
export default function rectangleLayout(node) {
|
|
14
|
+
const data = getHorizontal(node); // per-node horizontally resolved
|
|
15
|
+
const vertical_lines = getVertical(node); // single spanning vertical per parent
|
|
16
|
+
const child_vertical_lines = getChildVerticals(node); // one vertical per child edge
|
|
7
17
|
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
const horizontal_lines = data
|
|
19
|
+
.filter(d => d.parentId != null)
|
|
20
|
+
.map(d => ({
|
|
21
|
+
parentId: d.parentId,
|
|
22
|
+
childId: d.thisId,
|
|
23
|
+
x0: d.x0, x1: d.x1,
|
|
24
|
+
y: d.y0
|
|
25
|
+
}));
|
|
10
26
|
|
|
11
|
-
data
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return data;
|
|
15
|
-
}
|
|
27
|
+
return { data, vertical_lines, child_vertical_lines, horizontal_lines };
|
|
28
|
+
}
|
|
@@ -1,49 +1,60 @@
|
|
|
1
1
|
import numTips from "../utils/numTips.js"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Equal-angle layout
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Equal-angle layout for unrooted trees.
|
|
5
|
+
* - Precomputes ntips in O(n) to avoid repeated subtree counts
|
|
6
|
+
* - Uses angles in "π units" (0..2) to match existing API
|
|
7
|
+
* - Populates x,y positions from branchLength and angle
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
function annotateTipCounts(root) {
|
|
11
|
+
(function post(n) {
|
|
12
|
+
if (!n.children || n.children.length === 0) {
|
|
13
|
+
n.ntips = 1; return 1;
|
|
14
|
+
}
|
|
15
|
+
let sum = 0;
|
|
16
|
+
for (const c of n.children) sum += post(c);
|
|
17
|
+
n.ntips = sum;
|
|
18
|
+
return sum;
|
|
19
|
+
})(root);
|
|
20
|
+
return root;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
function equalAngleLayout(node) {
|
|
11
24
|
if (node.parent === null) {
|
|
12
|
-
|
|
13
|
-
node.start = 0.;
|
|
14
|
-
node.end = 2.;
|
|
15
|
-
node.angle = 0.;
|
|
16
|
-
node.ntips = numTips(node);
|
|
25
|
+
annotateTipCounts(node);
|
|
26
|
+
node.start = 0.; // guarantees no arcs overlap 0
|
|
27
|
+
node.end = 2.; // *π
|
|
28
|
+
node.angle = 0.; // irrelevant at root
|
|
29
|
+
node.ntips = numTips(node); // safe (already computed), left for compatibility
|
|
17
30
|
node.x = 0;
|
|
18
31
|
node.y = 0;
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
let lastStart = node.start;
|
|
22
35
|
|
|
23
|
-
for (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// the number of tips the child node has
|
|
27
|
-
child.ntips = numTips(child);
|
|
36
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
37
|
+
const child = node.children[i];
|
|
38
|
+
const arc = (node.end - node.start) * (child.ntips / node.ntips);
|
|
28
39
|
|
|
29
|
-
// assign proportion of arc to this child
|
|
30
|
-
arc = (node.end - node.start) * child.ntips / node.ntips;
|
|
31
40
|
child.start = lastStart;
|
|
32
|
-
child.end =
|
|
41
|
+
child.end = lastStart + arc;
|
|
33
42
|
|
|
34
|
-
// bisect the arc
|
|
43
|
+
// bisect the arc in π-units
|
|
35
44
|
child.angle = child.start + (child.end - child.start) / 2.;
|
|
36
45
|
lastStart = child.end;
|
|
37
46
|
|
|
38
|
-
// map to coordinates
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
// map to coordinates (convert π-units to radians by multiplying by Math.PI)
|
|
48
|
+
const theta = child.angle * Math.PI;
|
|
49
|
+
const bl = (child.branchLength ?? 0);
|
|
50
|
+
child.x = node.x + bl * Math.sin(theta);
|
|
51
|
+
child.y = node.y + bl * Math.cos(theta);
|
|
41
52
|
|
|
42
|
-
// climb up
|
|
43
53
|
equalAngleLayout(child);
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
|
|
46
56
|
return node;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
export default equalAngleLayout
|
|
59
|
+
export default equalAngleLayout
|
|
60
|
+
|
package/src/unrooted/unrooted.js
CHANGED
package/src/utils/edges.js
CHANGED
|
@@ -4,52 +4,23 @@
|
|
|
4
4
|
* are the $edge slot. I think.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export default function (df, rectangular = false) {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export default function edges(df, rectangular = false) {
|
|
8
|
+
const rows = [...df].sort((a, b) => a.thisId - b.thisId);
|
|
9
|
+
const byId = new Map(rows.map((r) => [r.thisId, r]));
|
|
10
|
+
const result = [];
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
for (const row of df) {
|
|
17
|
-
if (row.parentId === null) {
|
|
18
|
-
continue; // skip the root
|
|
19
|
-
}
|
|
20
|
-
parent = df[row.parentId];
|
|
21
|
-
if (parent === null || parent === undefined) continue;
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
if (row.parentId == null) continue;
|
|
14
|
+
const parent = byId.get(row.parentId);
|
|
15
|
+
if (!parent) continue;
|
|
22
16
|
|
|
23
17
|
if (rectangular) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
y1: row.y,
|
|
27
|
-
id1: row.thisId,
|
|
28
|
-
x2: parent.x,
|
|
29
|
-
y2: row.y,
|
|
30
|
-
id2: undefined
|
|
31
|
-
};
|
|
32
|
-
result.push(pair1);
|
|
33
|
-
var pair2 = {
|
|
34
|
-
x1: parent.x,
|
|
35
|
-
y1: row.y,
|
|
36
|
-
id1: undefined,
|
|
37
|
-
x2: parent.x,
|
|
38
|
-
y2: parent.y,
|
|
39
|
-
id2: row.parentId
|
|
40
|
-
};
|
|
41
|
-
result.push(pair2);
|
|
18
|
+
result.push({ x1: row.x, y1: row.y, id1: row.thisId, x2: parent.x, y2: row.y, id2: undefined });
|
|
19
|
+
result.push({ x1: parent.x, y1: row.y, id1: undefined, x2: parent.x, y2: parent.y, id2: row.parentId });
|
|
42
20
|
} else {
|
|
43
|
-
|
|
44
|
-
x1: row.x,
|
|
45
|
-
y1: row.y,
|
|
46
|
-
id1: row.thisId,
|
|
47
|
-
x2: parent.x,
|
|
48
|
-
y2: parent.y,
|
|
49
|
-
id2: row.parentId
|
|
50
|
-
};
|
|
51
|
-
result.push(pair3);
|
|
21
|
+
result.push({ x1: row.x, y1: row.y, id1: row.thisId, x2: parent.x, y2: parent.y, id2: row.parentId });
|
|
52
22
|
}
|
|
53
23
|
}
|
|
54
24
|
return result;
|
|
55
|
-
}
|
|
25
|
+
}
|
|
26
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build quick indexes for a fortified data frame.
|
|
3
|
+
*/
|
|
4
|
+
export function indexTree(df) {
|
|
5
|
+
const byId = new Map(df.map(r => [r.thisId, r]));
|
|
6
|
+
const children = new Map(df.map(r => [r.thisId, []]));
|
|
7
|
+
let root = null;
|
|
8
|
+
for (const r of df) {
|
|
9
|
+
if (r.parentId == null) { root = r.thisId; continue; }
|
|
10
|
+
children.get(r.parentId).push(r.thisId);
|
|
11
|
+
}
|
|
12
|
+
// depths (BFS)
|
|
13
|
+
const depth = new Map(); if (root != null) depth.set(root, 0);
|
|
14
|
+
const q = root != null ? [root] : [];
|
|
15
|
+
while (q.length) {
|
|
16
|
+
const u = q.shift();
|
|
17
|
+
for (const v of (children.get(u) || [])) {
|
|
18
|
+
depth.set(v, (depth.get(u) || 0) + 1);
|
|
19
|
+
q.push(v);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { byId, children, depth, root };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute the node->root path for highlighting (returns [tip,...,root]).
|
|
27
|
+
*/
|
|
28
|
+
export function pathToRoot(dfIndex, nodeId) {
|
|
29
|
+
const { byId } = dfIndex;
|
|
30
|
+
const path = [];
|
|
31
|
+
let cur = nodeId;
|
|
32
|
+
while (cur != null) {
|
|
33
|
+
path.push(cur);
|
|
34
|
+
const row = byId.get(cur);
|
|
35
|
+
cur = row?.parentId ?? null;
|
|
36
|
+
}
|
|
37
|
+
return path;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convert a path (node ids) to parent-child edge pairs.
|
|
42
|
+
*/
|
|
43
|
+
export function edgesOnPath(pathIds) {
|
|
44
|
+
const pairs = [];
|
|
45
|
+
for (let i = 0; i < pathIds.length - 1; i++) {
|
|
46
|
+
const child = pathIds[i], parent = pathIds[i + 1];
|
|
47
|
+
pairs.push({ child, parent });
|
|
48
|
+
}
|
|
49
|
+
return pairs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional: split segments at midpoints (useful for finer-grain highlighting).
|
|
54
|
+
*/
|
|
55
|
+
export function splitEdges(segments) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const s of segments) {
|
|
58
|
+
const mx = (s.x1 + s.x2) / 2, my = (s.y1 + s.y2) / 2;
|
|
59
|
+
out.push({ ...s, x2: mx, y2: my, half: "proximal" });
|
|
60
|
+
out.push({ ...s, x1: mx, y1: my, half: "distal" });
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
package/src/utils/meanAngle.js
CHANGED
|
@@ -6,40 +6,15 @@
|
|
|
6
6
|
* e.g. if the start/end angles are equal to pi, the sign must be flipped.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
export default function meanAngle(a, b) {
|
|
10
|
+
// normalize to [0, 2pi)
|
|
11
|
+
const norm = (θ) => (θ % (2*Math.PI) + 2*Math.PI) % (2*Math.PI);
|
|
12
|
+
a = norm(a); b = norm(b);
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
) {
|
|
17
|
-
return Math.PI;
|
|
18
|
-
}
|
|
14
|
+
// handle wraparound by averaging unit vectors
|
|
15
|
+
const X = (Math.cos(a) + Math.cos(b)) / 2;
|
|
16
|
+
const Y = (Math.sin(a) + Math.sin(b)) / 2;
|
|
17
|
+
if (X === 0 && Y === 0) return 0; // opposite directions, arbitrary
|
|
18
|
+
return norm(Math.atan2(Y, X));
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
// calculate the average sin and cosine for the angles
|
|
21
|
-
const X = (Math.cos(start) + Math.cos(end)) / 2,
|
|
22
|
-
Y = (Math.sin(start) + Math.sin(end)) / 2;
|
|
23
|
-
|
|
24
|
-
const signX = Math.sign(X),
|
|
25
|
-
signY = Math.sign(Y);
|
|
26
|
-
|
|
27
|
-
var meanAngle;
|
|
28
|
-
// adjustments for the sign of each of X, Y
|
|
29
|
-
if (signX === 1 && signY === 1) {
|
|
30
|
-
meanAngle = Math.atan(Y / X);
|
|
31
|
-
} else if (signX === 1 && signY === -1) {
|
|
32
|
-
meanAngle = 2 * Math.PI - Math.atan(Y / X);
|
|
33
|
-
} else if (signX === -1 && signY === 1) {
|
|
34
|
-
meanAngle = Math.PI - Math.atan(Y / X);
|
|
35
|
-
} else if (signX === -1 && signY === -1) {
|
|
36
|
-
meanAngle = Math.PI + Math.atan(Y / X);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// if the start or end angles are equal to PI (or close approximations)
|
|
40
|
-
// flip the sign.
|
|
41
|
-
return start.toString().indexOf("3.14159") > -1 ||
|
|
42
|
-
end.toString().indexOf("3.14159") > -1
|
|
43
|
-
? -meanAngle
|
|
44
|
-
: meanAngle;
|
|
45
|
-
}
|
package/src/utils/numTips.js
CHANGED
|
@@ -4,29 +4,24 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
function levelorder(root) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
result.push(curnode);
|
|
15
|
-
for (const child of curnode.children) {
|
|
16
|
-
queue.push(child);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return (result);
|
|
7
|
+
const queue = [root], result = [];
|
|
8
|
+
while (queue.length) {
|
|
9
|
+
const curnode = queue.shift(); // <- FIFO
|
|
10
|
+
result.push(curnode);
|
|
11
|
+
for (const child of curnode.children) queue.push(child);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
20
14
|
}
|
|
21
15
|
|
|
16
|
+
|
|
22
17
|
/**
|
|
23
18
|
* Count the number of tips that descend from this node
|
|
24
19
|
*/
|
|
25
20
|
|
|
26
|
-
export default function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
21
|
+
export default function(thisnode) {
|
|
22
|
+
var result = 0;
|
|
23
|
+
for (const node of levelorder(thisnode)) {
|
|
24
|
+
if (node.children.length == 0) result++;
|
|
25
|
+
}
|
|
26
|
+
return (result);
|
|
27
|
+
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
}
|
|
1
|
+
export function makeIndexById(rows, key = "thisId") {
|
|
2
|
+
return new Map(rows.map(d => [d[key], d]));
|
|
3
|
+
}
|
|
4
|
+
export default function parentFisheye(d, data) {
|
|
5
|
+
const byId = makeIndexById(data);
|
|
6
|
+
const parent = byId.get(d.parentId);
|
|
7
|
+
return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
|
|
8
|
+
}
|
|
9
|
+
|
package/src/utils/preorder.js
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recursive function for pre-order traversal of tree
|
|
2
|
+
* Recursive function for pre-order traversal of tree (returns array)
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
export function preorder(node, list = []) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
list.push(node);
|
|
6
|
+
for (let i = 0; i < (node.children?.length || 0); i++) {
|
|
7
|
+
list = preorder(node.children[i], list);
|
|
8
|
+
}
|
|
9
|
+
return list;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Iterative generator traversals (avoid recursion limits on large trees)
|
|
14
|
+
*/
|
|
15
|
+
export function* preorderIter(root) {
|
|
16
|
+
const stack = [root];
|
|
17
|
+
while (stack.length) {
|
|
18
|
+
const n = stack.pop();
|
|
19
|
+
yield n;
|
|
20
|
+
if (n.children) for (let i = n.children.length - 1; i >= 0; --i) stack.push(n.children[i]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function* postorderIter(root) {
|
|
25
|
+
const stack = [[root, 0]];
|
|
26
|
+
while (stack.length) {
|
|
27
|
+
const top = stack[stack.length - 1];
|
|
28
|
+
const [n, i] = top;
|
|
29
|
+
if (!n.children || i >= n.children.length) {
|
|
30
|
+
stack.pop();
|
|
31
|
+
yield n;
|
|
32
|
+
} else {
|
|
33
|
+
top[1] = i + 1;
|
|
34
|
+
stack.push([n.children[i], 0]);
|
|
9
35
|
}
|
|
10
|
-
|
|
36
|
+
}
|
|
11
37
|
}
|
|
38
|
+
|