@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/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
- input,
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 (onNodeClick) {
1459
+ if (effectiveOnNodeClick) {
1429
1460
  internalDots
1430
1461
  .style("cursor", "pointer")
1431
- .on("click", (event, d) => onNodeClick(d, event));
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 (onNodeClick) {
1819
+ if (effectiveOnNodeClick) {
1789
1820
  internalDots
1790
1821
  .style("cursor", "pointer")
1791
- .on("click", (event, d) => onNodeClick(d, event));
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 && onNodeClick) {
2085
+ if (internalNodeCircles && effectiveOnNodeClick) {
2055
2086
  nodes
2056
2087
  .filter((d) => !d.isTip)
2057
2088
  .style("cursor", "pointer")
2058
- .on("click", (event, d) => onNodeClick(d, event));
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'."