@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.js
CHANGED
|
@@ -1227,9 +1227,8 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
|
|
|
1227
1227
|
// by readTree()/randomTree()). Passing the same parsed tree object back in
|
|
1228
1228
|
// across re-renders (e.g. after mutating it with rotate()) keeps node ids
|
|
1229
1229
|
// stable, which onNodeClick below relies on.
|
|
1230
|
-
function drawPhylogeny(
|
|
1231
|
-
|
|
1232
|
-
{
|
|
1230
|
+
function drawPhylogeny(input, options = {}) {
|
|
1231
|
+
const {
|
|
1233
1232
|
layout = "rect", // rect/radial/unrooted
|
|
1234
1233
|
width = 800,
|
|
1235
1234
|
height = 800,
|
|
@@ -1240,12 +1239,13 @@ function drawPhylogeny(
|
|
|
1240
1239
|
tipLabels = true,
|
|
1241
1240
|
labelFontSize = 10, // font size (px) for tip labels
|
|
1242
1241
|
tipRadius, // px radius of tip dots; defaults to each layout's original size
|
|
1243
|
-
internalNodeCircles = false, // draw a circle at every internal (non-tip) node
|
|
1244
1242
|
internalNodeRadius = 3, // px radius for internal node circles
|
|
1245
1243
|
nodeLabels = false, // draw text labels at internal nodes (e.g. clade/support labels)
|
|
1246
1244
|
nodeLabelFontSize, // defaults to labelFontSize
|
|
1247
1245
|
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
1248
1246
|
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
1247
|
+
container, // DOM element or CSS selector — if given, drawPhylogeny mounts the SVG itself instead of just returning it
|
|
1248
|
+
rotateOnClick = false, // click an internal node circle to rotate its children in place and auto-redraw (requires container)
|
|
1249
1249
|
onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
|
|
1250
1250
|
showTooltips = true,
|
|
1251
1251
|
tooltipFormatter = (d, rtt) =>
|
|
@@ -1255,8 +1255,10 @@ function drawPhylogeny(
|
|
|
1255
1255
|
highlightTips = [], // array of tip labels or ids for static highlight (optional)
|
|
1256
1256
|
highlightStroke = "#e63946",
|
|
1257
1257
|
highlightWidth = 2.5
|
|
1258
|
-
} =
|
|
1259
|
-
|
|
1258
|
+
} = options;
|
|
1259
|
+
// rotateOnClick needs internal node circles to click on; auto-enable them
|
|
1260
|
+
// unless the caller explicitly said otherwise.
|
|
1261
|
+
const internalNodeCircles = options.internalNodeCircles ?? rotateOnClick;
|
|
1260
1262
|
|
|
1261
1263
|
// shared helpers
|
|
1262
1264
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
@@ -1264,6 +1266,35 @@ function drawPhylogeny(
|
|
|
1264
1266
|
const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
|
|
1265
1267
|
? input
|
|
1266
1268
|
: readTree(input);
|
|
1269
|
+
|
|
1270
|
+
const mountEl = typeof container === "string"
|
|
1271
|
+
? (typeof document !== "undefined" ? document.querySelector(container) : null)
|
|
1272
|
+
: (container ?? null);
|
|
1273
|
+
|
|
1274
|
+
if (rotateOnClick && !mountEl) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
"rotateOnClick requires a `container` (a DOM element or CSS selector) so drawPhylogeny can mount the redrawn SVG after each rotation."
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// fires the caller's onNodeClick (if any), then handles the rotate+redraw itself
|
|
1281
|
+
const effectiveOnNodeClick = (onNodeClick || rotateOnClick)
|
|
1282
|
+
? (node, event) => {
|
|
1283
|
+
if (typeof onNodeClick === "function") onNodeClick(node, event);
|
|
1284
|
+
if (rotateOnClick) {
|
|
1285
|
+
rotate(parsedTree, node.thisId);
|
|
1286
|
+
drawPhylogeny(parsedTree, options);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
: undefined;
|
|
1290
|
+
|
|
1291
|
+
function mount(svgNode) {
|
|
1292
|
+
if (mountEl) {
|
|
1293
|
+
mountEl.innerHTML = "";
|
|
1294
|
+
mountEl.appendChild(svgNode);
|
|
1295
|
+
}
|
|
1296
|
+
return svgNode;
|
|
1297
|
+
}
|
|
1267
1298
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
1268
1299
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
1269
1300
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1404,10 +1435,10 @@ function drawPhylogeny(
|
|
|
1404
1435
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1405
1436
|
}
|
|
1406
1437
|
|
|
1407
|
-
if (
|
|
1438
|
+
if (effectiveOnNodeClick) {
|
|
1408
1439
|
internalDots
|
|
1409
1440
|
.style("cursor", "pointer")
|
|
1410
|
-
.on("click", (event, d) =>
|
|
1441
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1411
1442
|
}
|
|
1412
1443
|
}
|
|
1413
1444
|
|
|
@@ -1538,7 +1569,7 @@ function drawPhylogeny(
|
|
|
1538
1569
|
});
|
|
1539
1570
|
}
|
|
1540
1571
|
|
|
1541
|
-
return svg.node();
|
|
1572
|
+
return mount(svg.node());
|
|
1542
1573
|
} else if (layout === "radial") {
|
|
1543
1574
|
// RADIAL LAYOUT
|
|
1544
1575
|
if (width !== height) {
|
|
@@ -1764,10 +1795,10 @@ function drawPhylogeny(
|
|
|
1764
1795
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1765
1796
|
}
|
|
1766
1797
|
|
|
1767
|
-
if (
|
|
1798
|
+
if (effectiveOnNodeClick) {
|
|
1768
1799
|
internalDots
|
|
1769
1800
|
.style("cursor", "pointer")
|
|
1770
|
-
.on("click", (event, d) =>
|
|
1801
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
1771
1802
|
}
|
|
1772
1803
|
}
|
|
1773
1804
|
|
|
@@ -1966,7 +1997,7 @@ function drawPhylogeny(
|
|
|
1966
1997
|
});
|
|
1967
1998
|
}
|
|
1968
1999
|
|
|
1969
|
-
return svg.node();
|
|
2000
|
+
return mount(svg.node());
|
|
1970
2001
|
} else if (layout === "unrooted") {
|
|
1971
2002
|
// UNROOTED LAYOUT
|
|
1972
2003
|
const unrootedPhylo = unrooted(parsedTree);
|
|
@@ -2030,11 +2061,11 @@ function drawPhylogeny(
|
|
|
2030
2061
|
.attr("stroke-width", 2)
|
|
2031
2062
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
2032
2063
|
|
|
2033
|
-
if (internalNodeCircles &&
|
|
2064
|
+
if (internalNodeCircles && effectiveOnNodeClick) {
|
|
2034
2065
|
nodes
|
|
2035
2066
|
.filter((d) => !d.isTip)
|
|
2036
2067
|
.style("cursor", "pointer")
|
|
2037
|
-
.on("click", (event, d) =>
|
|
2068
|
+
.on("click", (event, d) => effectiveOnNodeClick(d, event));
|
|
2038
2069
|
}
|
|
2039
2070
|
|
|
2040
2071
|
if (nodeLabels) {
|
|
@@ -2199,7 +2230,7 @@ function drawPhylogeny(
|
|
|
2199
2230
|
});
|
|
2200
2231
|
}
|
|
2201
2232
|
|
|
2202
|
-
return svg.node();
|
|
2233
|
+
return mount(svg.node());
|
|
2203
2234
|
} else {
|
|
2204
2235
|
throw new Error(
|
|
2205
2236
|
"Unsupported layout type. Use 'rect', 'radial', or 'unrooted'."
|