@euphrasiologist/lwphylo 1.5.0 → 1.5.1
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 +6 -1
- package/dist/drawPhylogeny.cjs +60 -15
- package/dist/drawPhylogeny.cjs.map +1 -1
- package/dist/drawPhylogeny.esm.js +60 -15
- 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 +46 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -816,6 +816,20 @@ function unrooted (node) {
|
|
|
816
816
|
return data;
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
+
/**
|
|
820
|
+
* Reverse the child order at one node, flipping which side of the tree
|
|
821
|
+
* that clade's descendants are drawn on. Defaults to the root. Mutates
|
|
822
|
+
* the tree in place.
|
|
823
|
+
*/
|
|
824
|
+
function rotate(tree, nodeId = tree.id) {
|
|
825
|
+
const target = preorder(tree).find((n) => n.id === nodeId);
|
|
826
|
+
if (!target) {
|
|
827
|
+
throw new Error(`No node with id ${nodeId} found in tree`);
|
|
828
|
+
}
|
|
829
|
+
target.children.reverse();
|
|
830
|
+
return tree;
|
|
831
|
+
}
|
|
832
|
+
|
|
819
833
|
/**
|
|
820
834
|
* Parse a Newick tree string into a doubly-linked list of JS Objects.
|
|
821
835
|
* Assigns labels, branch lengths, and node IDs (tips before internals if input emits them that way).
|
|
@@ -929,9 +943,8 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
|
|
|
929
943
|
// by readTree()/randomTree()). Passing the same parsed tree object back in
|
|
930
944
|
// across re-renders (e.g. after mutating it with rotate()) keeps node ids
|
|
931
945
|
// stable, which onNodeClick below relies on.
|
|
932
|
-
function drawPhylogeny(
|
|
933
|
-
|
|
934
|
-
{
|
|
946
|
+
function drawPhylogeny(input, options = {}) {
|
|
947
|
+
const {
|
|
935
948
|
layout = "rect", // rect/radial/unrooted
|
|
936
949
|
width = 800,
|
|
937
950
|
height = 800,
|
|
@@ -942,12 +955,13 @@ function drawPhylogeny(
|
|
|
942
955
|
tipLabels = true,
|
|
943
956
|
labelFontSize = 10, // font size (px) for tip labels
|
|
944
957
|
tipRadius, // px radius of tip dots; defaults to each layout's original size
|
|
945
|
-
internalNodeCircles = false, // draw a circle at every internal (non-tip) node
|
|
946
958
|
internalNodeRadius = 3, // px radius for internal node circles
|
|
947
959
|
nodeLabels = false, // draw text labels at internal nodes (e.g. clade/support labels)
|
|
948
960
|
nodeLabelFontSize, // defaults to labelFontSize
|
|
949
961
|
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
950
962
|
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
963
|
+
container, // DOM element or CSS selector — if given, drawPhylogeny mounts the SVG itself instead of just returning it
|
|
964
|
+
rotateOnClick = false, // click an internal node circle to rotate its children in place and auto-redraw (requires container)
|
|
951
965
|
onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
|
|
952
966
|
showTooltips = true,
|
|
953
967
|
tooltipFormatter = (d, rtt) =>
|
|
@@ -957,8 +971,10 @@ function drawPhylogeny(
|
|
|
957
971
|
highlightTips = [], // array of tip labels or ids for static highlight (optional)
|
|
958
972
|
highlightStroke = "#e63946",
|
|
959
973
|
highlightWidth = 2.5
|
|
960
|
-
} =
|
|
961
|
-
|
|
974
|
+
} = options;
|
|
975
|
+
// rotateOnClick needs internal node circles to click on; auto-enable them
|
|
976
|
+
// unless the caller explicitly said otherwise.
|
|
977
|
+
const internalNodeCircles = options.internalNodeCircles ?? rotateOnClick;
|
|
962
978
|
|
|
963
979
|
// shared helpers
|
|
964
980
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
@@ -966,6 +982,35 @@ function drawPhylogeny(
|
|
|
966
982
|
const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
|
|
967
983
|
? input
|
|
968
984
|
: readTree(input);
|
|
985
|
+
|
|
986
|
+
const mountEl = typeof container === "string"
|
|
987
|
+
? (typeof document !== "undefined" ? document.querySelector(container) : null)
|
|
988
|
+
: (container ?? null);
|
|
989
|
+
|
|
990
|
+
if (rotateOnClick && !mountEl) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
"rotateOnClick requires a `container` (a DOM element or CSS selector) so drawPhylogeny can mount the redrawn SVG after each rotation."
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// fires the caller's onNodeClick (if any), then handles the rotate+redraw itself
|
|
997
|
+
const effectiveOnNodeClick = (onNodeClick || rotateOnClick)
|
|
998
|
+
? (node, event) => {
|
|
999
|
+
if (typeof onNodeClick === "function") onNodeClick(node, event);
|
|
1000
|
+
if (rotateOnClick) {
|
|
1001
|
+
rotate(parsedTree, node.thisId);
|
|
1002
|
+
drawPhylogeny(parsedTree, options);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
: undefined;
|
|
1006
|
+
|
|
1007
|
+
function mount(svgNode) {
|
|
1008
|
+
if (mountEl) {
|
|
1009
|
+
mountEl.innerHTML = "";
|
|
1010
|
+
mountEl.appendChild(svgNode);
|
|
1011
|
+
}
|
|
1012
|
+
return svgNode;
|
|
1013
|
+
}
|
|
969
1014
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
970
1015
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
971
1016
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1106,10 +1151,10 @@ function drawPhylogeny(
|
|
|
1106
1151
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1107
1152
|
}
|
|
1108
1153
|
|
|
1109
|
-
if (
|
|
1154
|
+
if (effectiveOnNodeClick) {
|
|
1110
1155
|
internalDots
|
|
1111
1156
|
.style("cursor", "pointer")
|
|
1112
|
-
.on("click", (event, d) =>
|
|
1157
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1113
1158
|
}
|
|
1114
1159
|
}
|
|
1115
1160
|
|
|
@@ -1240,7 +1285,7 @@ function drawPhylogeny(
|
|
|
1240
1285
|
});
|
|
1241
1286
|
}
|
|
1242
1287
|
|
|
1243
|
-
return svg.node();
|
|
1288
|
+
return mount(svg.node());
|
|
1244
1289
|
} else if (layout === "radial") {
|
|
1245
1290
|
// RADIAL LAYOUT
|
|
1246
1291
|
if (width !== height) {
|
|
@@ -1466,10 +1511,10 @@ function drawPhylogeny(
|
|
|
1466
1511
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1467
1512
|
}
|
|
1468
1513
|
|
|
1469
|
-
if (
|
|
1514
|
+
if (effectiveOnNodeClick) {
|
|
1470
1515
|
internalDots
|
|
1471
1516
|
.style("cursor", "pointer")
|
|
1472
|
-
.on("click", (event, d) =>
|
|
1517
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1473
1518
|
}
|
|
1474
1519
|
}
|
|
1475
1520
|
|
|
@@ -1668,7 +1713,7 @@ function drawPhylogeny(
|
|
|
1668
1713
|
});
|
|
1669
1714
|
}
|
|
1670
1715
|
|
|
1671
|
-
return svg.node();
|
|
1716
|
+
return mount(svg.node());
|
|
1672
1717
|
} else if (layout === "unrooted") {
|
|
1673
1718
|
// UNROOTED LAYOUT
|
|
1674
1719
|
const unrootedPhylo = unrooted(parsedTree);
|
|
@@ -1732,11 +1777,11 @@ function drawPhylogeny(
|
|
|
1732
1777
|
.attr("stroke-width", 2)
|
|
1733
1778
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
1734
1779
|
|
|
1735
|
-
if (internalNodeCircles &&
|
|
1780
|
+
if (internalNodeCircles && effectiveOnNodeClick) {
|
|
1736
1781
|
nodes
|
|
1737
1782
|
.filter((d) => !d.isTip)
|
|
1738
1783
|
.style("cursor", "pointer")
|
|
1739
|
-
.on("click", (event, d) =>
|
|
1784
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1740
1785
|
}
|
|
1741
1786
|
|
|
1742
1787
|
if (nodeLabels) {
|
|
@@ -1901,7 +1946,7 @@ function drawPhylogeny(
|
|
|
1901
1946
|
});
|
|
1902
1947
|
}
|
|
1903
1948
|
|
|
1904
|
-
return svg.node();
|
|
1949
|
+
return mount(svg.node());
|
|
1905
1950
|
} else {
|
|
1906
1951
|
throw new Error(
|
|
1907
1952
|
"Unsupported layout type. Use 'rect', 'radial', or 'unrooted'."
|