@euphrasiologist/lwphylo 1.4.0 → 1.5.0
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 +3 -2
- package/dist/drawPhylogeny.cjs +29 -4
- package/dist/drawPhylogeny.cjs.map +1 -1
- package/dist/drawPhylogeny.esm.js +29 -4
- package/dist/drawPhylogeny.esm.js.map +1 -1
- package/dist/drawPhylogeny.umd.js +1 -1
- package/dist/drawPhylogeny.umd.js.map +1 -1
- package/dist/index.cjs +29 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,16 +10,17 @@ Visit https://euphrasiologist.github.io/lwPhylo/ to see examples and live render
|
|
|
10
10
|
|
|
11
11
|
Newick trees can be parsed using the `readTree()` function. This object can then be wrapped in three main functions; `rectangleLayout()` to produce a "regular" phylogenetic tree, `radialLayout()` to produce a circular phylogeny, and `unrooted()` to produce an unrooted tree via the equal angle layout algorithm.
|
|
12
12
|
|
|
13
|
-
Need a tree to experiment with? `randomTree(nTips, { maxBranchLength, labelPrefix, seed })` generates a random bifurcating tree in the same node shape as `readTree()`, ready to pass straight into any of the layout functions. `toNewick(tree)` serializes one of these parsed tree objects back to a Newick string
|
|
13
|
+
Need a tree to experiment with? `randomTree(nTips, { maxBranchLength, labelPrefix, seed })` generates a random bifurcating tree in the same node shape as `readTree()`, ready to pass straight into any of the layout functions. `toNewick(tree)` serializes one of these parsed tree objects back to a Newick string.
|
|
14
14
|
|
|
15
15
|
`ladderize(tree, { ascending })` and `rotate(tree, nodeId)` change tip order by reordering a node's children in place — ladderize sorts every clade by descendant tip count (smallest first by default), rotate flips the child order at one node (the root, if no id is given).
|
|
16
16
|
|
|
17
|
-
`drawPhylogeny(
|
|
17
|
+
`drawPhylogeny(input, options)` accepts either a Newick string or an already-parsed tree object (from `readTree()`/`randomTree()`) as `input`. Passing the same parsed tree object back in across re-renders — e.g. after mutating it with `rotate()` — keeps node ids stable, which `onNodeClick` (below) relies on. Options, in addition to `layout`/`width`/`height`/`tipLabels`/`labelFontSize`/`highlightTips`:
|
|
18
18
|
- `tipRadius` — px radius of tip circles.
|
|
19
19
|
- `internalNodeCircles` (bool) + `internalNodeRadius` — draw a circle at every internal node.
|
|
20
20
|
- `nodeLabels` (bool) + `nodeLabelFontSize` — draw text labels (e.g. clade/support values) at internal nodes that have one.
|
|
21
21
|
- `scaleBar` — `true` for an auto-sized branch-length scale bar, a number for an explicit length in branch-length units, or `{ length, x, y, label }` for full control.
|
|
22
22
|
- `alignTipLabels` (bool, rect & radial layouts) — align tip labels to a common column/ring, with dashed guide lines back to each tip's true position.
|
|
23
|
+
- `onNodeClick(node, event)` — fires when an internal node circle is clicked (requires `internalNodeCircles: true`). Combine with `rotate()` for click-to-rotate: `onNodeClick: (node) => { rotate(tree, node.thisId); redraw(); }`.
|
|
23
24
|
|
|
24
25
|
### Acknowledgements
|
|
25
26
|
|
package/dist/drawPhylogeny.cjs
CHANGED
|
@@ -946,8 +946,12 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
|
|
|
946
946
|
.text(opts.label ?? String(length));
|
|
947
947
|
}
|
|
948
948
|
|
|
949
|
+
// input: a Newick string, or an already-parsed tree (the node shape returned
|
|
950
|
+
// by readTree()/randomTree()). Passing the same parsed tree object back in
|
|
951
|
+
// across re-renders (e.g. after mutating it with rotate()) keeps node ids
|
|
952
|
+
// stable, which onNodeClick below relies on.
|
|
949
953
|
function drawPhylogeny(
|
|
950
|
-
|
|
954
|
+
input,
|
|
951
955
|
{
|
|
952
956
|
layout = "rect", // rect/radial/unrooted
|
|
953
957
|
width = 800,
|
|
@@ -965,6 +969,7 @@ function drawPhylogeny(
|
|
|
965
969
|
nodeLabelFontSize, // defaults to labelFontSize
|
|
966
970
|
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
967
971
|
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
972
|
+
onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
|
|
968
973
|
showTooltips = true,
|
|
969
974
|
tooltipFormatter = (d, rtt) =>
|
|
970
975
|
`${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
|
|
@@ -979,6 +984,9 @@ function drawPhylogeny(
|
|
|
979
984
|
// shared helpers
|
|
980
985
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
981
986
|
const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
|
|
987
|
+
const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
|
|
988
|
+
? input
|
|
989
|
+
: readTree(input);
|
|
982
990
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
983
991
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
984
992
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1007,7 +1015,7 @@ function drawPhylogeny(
|
|
|
1007
1015
|
|
|
1008
1016
|
if (layout === "rect") {
|
|
1009
1017
|
// RECTANGULAR LAYOUT
|
|
1010
|
-
const tree_df = rectangleLayout(
|
|
1018
|
+
const tree_df = rectangleLayout(parsedTree);
|
|
1011
1019
|
const horizontal = tree_df.horizontal_lines;
|
|
1012
1020
|
const vertical = tree_df.vertical_lines;
|
|
1013
1021
|
const tips = horizontal.filter((d) => d.isTip);
|
|
@@ -1118,6 +1126,12 @@ function drawPhylogeny(
|
|
|
1118
1126
|
.append("title")
|
|
1119
1127
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1120
1128
|
}
|
|
1129
|
+
|
|
1130
|
+
if (onNodeClick) {
|
|
1131
|
+
internalDots
|
|
1132
|
+
.style("cursor", "pointer")
|
|
1133
|
+
.on("click", (event, d) => onNodeClick(d, event));
|
|
1134
|
+
}
|
|
1121
1135
|
}
|
|
1122
1136
|
|
|
1123
1137
|
// internal node labels (optional)
|
|
@@ -1253,7 +1267,6 @@ function drawPhylogeny(
|
|
|
1253
1267
|
if (width !== height) {
|
|
1254
1268
|
throw new Error("width and height must be the same for radial layout");
|
|
1255
1269
|
}
|
|
1256
|
-
const parsedTree = readTree(treeText);
|
|
1257
1270
|
const rad = radialLayout(parsedTree, {
|
|
1258
1271
|
angleStrategy: "fan",
|
|
1259
1272
|
arcsStyle: "fan"
|
|
@@ -1473,6 +1486,12 @@ function drawPhylogeny(
|
|
|
1473
1486
|
.append("title")
|
|
1474
1487
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1475
1488
|
}
|
|
1489
|
+
|
|
1490
|
+
if (onNodeClick) {
|
|
1491
|
+
internalDots
|
|
1492
|
+
.style("cursor", "pointer")
|
|
1493
|
+
.on("click", (event, d) => onNodeClick(d, event));
|
|
1494
|
+
}
|
|
1476
1495
|
}
|
|
1477
1496
|
|
|
1478
1497
|
// ===== INTERNAL NODE LABELS (optional) =====
|
|
@@ -1673,7 +1692,6 @@ function drawPhylogeny(
|
|
|
1673
1692
|
return svg.node();
|
|
1674
1693
|
} else if (layout === "unrooted") {
|
|
1675
1694
|
// UNROOTED LAYOUT
|
|
1676
|
-
const parsedTree = readTree(treeText);
|
|
1677
1695
|
const unrootedPhylo = unrooted(parsedTree);
|
|
1678
1696
|
|
|
1679
1697
|
const w = width;
|
|
@@ -1735,6 +1753,13 @@ function drawPhylogeny(
|
|
|
1735
1753
|
.attr("stroke-width", 2)
|
|
1736
1754
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
1737
1755
|
|
|
1756
|
+
if (internalNodeCircles && onNodeClick) {
|
|
1757
|
+
nodes
|
|
1758
|
+
.filter((d) => !d.isTip)
|
|
1759
|
+
.style("cursor", "pointer")
|
|
1760
|
+
.on("click", (event, d) => onNodeClick(d, event));
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1738
1763
|
if (nodeLabels) {
|
|
1739
1764
|
const labeledInternalNodes = unrootedPhylo.data.filter((d) => !d.isTip && d.thisLabel);
|
|
1740
1765
|
group
|