@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
package/dist/index.cjs
CHANGED
|
@@ -1248,9 +1248,8 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
|
|
|
1248
1248
|
// by readTree()/randomTree()). Passing the same parsed tree object back in
|
|
1249
1249
|
// across re-renders (e.g. after mutating it with rotate()) keeps node ids
|
|
1250
1250
|
// stable, which onNodeClick below relies on.
|
|
1251
|
-
function drawPhylogeny(
|
|
1252
|
-
|
|
1253
|
-
{
|
|
1251
|
+
function drawPhylogeny(input, options = {}) {
|
|
1252
|
+
const {
|
|
1254
1253
|
layout = "rect", // rect/radial/unrooted
|
|
1255
1254
|
width = 800,
|
|
1256
1255
|
height = 800,
|
|
@@ -1261,12 +1260,13 @@ function drawPhylogeny(
|
|
|
1261
1260
|
tipLabels = true,
|
|
1262
1261
|
labelFontSize = 10, // font size (px) for tip labels
|
|
1263
1262
|
tipRadius, // px radius of tip dots; defaults to each layout's original size
|
|
1264
|
-
internalNodeCircles = false, // draw a circle at every internal (non-tip) node
|
|
1265
1263
|
internalNodeRadius = 3, // px radius for internal node circles
|
|
1266
1264
|
nodeLabels = false, // draw text labels at internal nodes (e.g. clade/support labels)
|
|
1267
1265
|
nodeLabelFontSize, // defaults to labelFontSize
|
|
1268
1266
|
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
1269
1267
|
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
1268
|
+
container, // DOM element or CSS selector — if given, drawPhylogeny mounts the SVG itself instead of just returning it
|
|
1269
|
+
rotateOnClick = false, // click an internal node circle to rotate its children in place and auto-redraw (requires container)
|
|
1270
1270
|
onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
|
|
1271
1271
|
showTooltips = true,
|
|
1272
1272
|
tooltipFormatter = (d, rtt) =>
|
|
@@ -1276,8 +1276,10 @@ function drawPhylogeny(
|
|
|
1276
1276
|
highlightTips = [], // array of tip labels or ids for static highlight (optional)
|
|
1277
1277
|
highlightStroke = "#e63946",
|
|
1278
1278
|
highlightWidth = 2.5
|
|
1279
|
-
} =
|
|
1280
|
-
|
|
1279
|
+
} = options;
|
|
1280
|
+
// rotateOnClick needs internal node circles to click on; auto-enable them
|
|
1281
|
+
// unless the caller explicitly said otherwise.
|
|
1282
|
+
const internalNodeCircles = options.internalNodeCircles ?? rotateOnClick;
|
|
1281
1283
|
|
|
1282
1284
|
// shared helpers
|
|
1283
1285
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
@@ -1285,6 +1287,35 @@ function drawPhylogeny(
|
|
|
1285
1287
|
const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
|
|
1286
1288
|
? input
|
|
1287
1289
|
: readTree(input);
|
|
1290
|
+
|
|
1291
|
+
const mountEl = typeof container === "string"
|
|
1292
|
+
? (typeof document !== "undefined" ? document.querySelector(container) : null)
|
|
1293
|
+
: (container ?? null);
|
|
1294
|
+
|
|
1295
|
+
if (rotateOnClick && !mountEl) {
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
"rotateOnClick requires a `container` (a DOM element or CSS selector) so drawPhylogeny can mount the redrawn SVG after each rotation."
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// fires the caller's onNodeClick (if any), then handles the rotate+redraw itself
|
|
1302
|
+
const effectiveOnNodeClick = (onNodeClick || rotateOnClick)
|
|
1303
|
+
? (node, event) => {
|
|
1304
|
+
if (typeof onNodeClick === "function") onNodeClick(node, event);
|
|
1305
|
+
if (rotateOnClick) {
|
|
1306
|
+
rotate(parsedTree, node.thisId);
|
|
1307
|
+
drawPhylogeny(parsedTree, options);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
: undefined;
|
|
1311
|
+
|
|
1312
|
+
function mount(svgNode) {
|
|
1313
|
+
if (mountEl) {
|
|
1314
|
+
mountEl.innerHTML = "";
|
|
1315
|
+
mountEl.appendChild(svgNode);
|
|
1316
|
+
}
|
|
1317
|
+
return svgNode;
|
|
1318
|
+
}
|
|
1288
1319
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
1289
1320
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
1290
1321
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1425,10 +1456,10 @@ function drawPhylogeny(
|
|
|
1425
1456
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1426
1457
|
}
|
|
1427
1458
|
|
|
1428
|
-
if (
|
|
1459
|
+
if (effectiveOnNodeClick) {
|
|
1429
1460
|
internalDots
|
|
1430
1461
|
.style("cursor", "pointer")
|
|
1431
|
-
.on("click", (event, d) =>
|
|
1462
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1432
1463
|
}
|
|
1433
1464
|
}
|
|
1434
1465
|
|
|
@@ -1559,7 +1590,7 @@ function drawPhylogeny(
|
|
|
1559
1590
|
});
|
|
1560
1591
|
}
|
|
1561
1592
|
|
|
1562
|
-
return svg.node();
|
|
1593
|
+
return mount(svg.node());
|
|
1563
1594
|
} else if (layout === "radial") {
|
|
1564
1595
|
// RADIAL LAYOUT
|
|
1565
1596
|
if (width !== height) {
|
|
@@ -1785,10 +1816,10 @@ function drawPhylogeny(
|
|
|
1785
1816
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1786
1817
|
}
|
|
1787
1818
|
|
|
1788
|
-
if (
|
|
1819
|
+
if (effectiveOnNodeClick) {
|
|
1789
1820
|
internalDots
|
|
1790
1821
|
.style("cursor", "pointer")
|
|
1791
|
-
.on("click", (event, d) =>
|
|
1822
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1792
1823
|
}
|
|
1793
1824
|
}
|
|
1794
1825
|
|
|
@@ -1987,7 +2018,7 @@ function drawPhylogeny(
|
|
|
1987
2018
|
});
|
|
1988
2019
|
}
|
|
1989
2020
|
|
|
1990
|
-
return svg.node();
|
|
2021
|
+
return mount(svg.node());
|
|
1991
2022
|
} else if (layout === "unrooted") {
|
|
1992
2023
|
// UNROOTED LAYOUT
|
|
1993
2024
|
const unrootedPhylo = unrooted(parsedTree);
|
|
@@ -2051,11 +2082,11 @@ function drawPhylogeny(
|
|
|
2051
2082
|
.attr("stroke-width", 2)
|
|
2052
2083
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
2053
2084
|
|
|
2054
|
-
if (internalNodeCircles &&
|
|
2085
|
+
if (internalNodeCircles && effectiveOnNodeClick) {
|
|
2055
2086
|
nodes
|
|
2056
2087
|
.filter((d) => !d.isTip)
|
|
2057
2088
|
.style("cursor", "pointer")
|
|
2058
|
-
.on("click", (event, d) =>
|
|
2089
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
2059
2090
|
}
|
|
2060
2091
|
|
|
2061
2092
|
if (nodeLabels) {
|
|
@@ -2220,7 +2251,7 @@ function drawPhylogeny(
|
|
|
2220
2251
|
});
|
|
2221
2252
|
}
|
|
2222
2253
|
|
|
2223
|
-
return svg.node();
|
|
2254
|
+
return mount(svg.node());
|
|
2224
2255
|
} else {
|
|
2225
2256
|
throw new Error(
|
|
2226
2257
|
"Unsupported layout type. Use 'rect', 'radial', or 'unrooted'."
|