@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/dist/index.cjs CHANGED
@@ -1244,8 +1244,12 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
1244
1244
  .text(opts.label ?? String(length));
1245
1245
  }
1246
1246
 
1247
+ // input: a Newick string, or an already-parsed tree (the node shape returned
1248
+ // by readTree()/randomTree()). Passing the same parsed tree object back in
1249
+ // across re-renders (e.g. after mutating it with rotate()) keeps node ids
1250
+ // stable, which onNodeClick below relies on.
1247
1251
  function drawPhylogeny(
1248
- treeText,
1252
+ input,
1249
1253
  {
1250
1254
  layout = "rect", // rect/radial/unrooted
1251
1255
  width = 800,
@@ -1263,6 +1267,7 @@ function drawPhylogeny(
1263
1267
  nodeLabelFontSize, // defaults to labelFontSize
1264
1268
  scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
1265
1269
  alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
1270
+ onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
1266
1271
  showTooltips = true,
1267
1272
  tooltipFormatter = (d, rtt) =>
1268
1273
  `${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
@@ -1277,6 +1282,9 @@ function drawPhylogeny(
1277
1282
  // shared helpers
1278
1283
  const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
1279
1284
  const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
1285
+ const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
1286
+ ? input
1287
+ : readTree(input);
1280
1288
  // Works for both radial (uses `r`) and rect (uses `x1`).
1281
1289
  // Falls back to summing branchLength up to the root if neither is present.
1282
1290
  function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
@@ -1305,7 +1313,7 @@ function drawPhylogeny(
1305
1313
 
1306
1314
  if (layout === "rect") {
1307
1315
  // RECTANGULAR LAYOUT
1308
- const tree_df = rectangleLayout(readTree(treeText));
1316
+ const tree_df = rectangleLayout(parsedTree);
1309
1317
  const horizontal = tree_df.horizontal_lines;
1310
1318
  const vertical = tree_df.vertical_lines;
1311
1319
  const tips = horizontal.filter((d) => d.isTip);
@@ -1416,6 +1424,12 @@ function drawPhylogeny(
1416
1424
  .append("title")
1417
1425
  .text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
1418
1426
  }
1427
+
1428
+ if (onNodeClick) {
1429
+ internalDots
1430
+ .style("cursor", "pointer")
1431
+ .on("click", (event, d) => onNodeClick(d, event));
1432
+ }
1419
1433
  }
1420
1434
 
1421
1435
  // internal node labels (optional)
@@ -1551,7 +1565,6 @@ function drawPhylogeny(
1551
1565
  if (width !== height) {
1552
1566
  throw new Error("width and height must be the same for radial layout");
1553
1567
  }
1554
- const parsedTree = readTree(treeText);
1555
1568
  const rad = radialLayout(parsedTree, {
1556
1569
  angleStrategy: "fan",
1557
1570
  arcsStyle: "fan"
@@ -1771,6 +1784,12 @@ function drawPhylogeny(
1771
1784
  .append("title")
1772
1785
  .text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
1773
1786
  }
1787
+
1788
+ if (onNodeClick) {
1789
+ internalDots
1790
+ .style("cursor", "pointer")
1791
+ .on("click", (event, d) => onNodeClick(d, event));
1792
+ }
1774
1793
  }
1775
1794
 
1776
1795
  // ===== INTERNAL NODE LABELS (optional) =====
@@ -1971,7 +1990,6 @@ function drawPhylogeny(
1971
1990
  return svg.node();
1972
1991
  } else if (layout === "unrooted") {
1973
1992
  // UNROOTED LAYOUT
1974
- const parsedTree = readTree(treeText);
1975
1993
  const unrootedPhylo = unrooted(parsedTree);
1976
1994
 
1977
1995
  const w = width;
@@ -2033,6 +2051,13 @@ function drawPhylogeny(
2033
2051
  .attr("stroke-width", 2)
2034
2052
  .attr("fill", (d) => (d.isTip ? "black" : "white"));
2035
2053
 
2054
+ if (internalNodeCircles && onNodeClick) {
2055
+ nodes
2056
+ .filter((d) => !d.isTip)
2057
+ .style("cursor", "pointer")
2058
+ .on("click", (event, d) => onNodeClick(d, event));
2059
+ }
2060
+
2036
2061
  if (nodeLabels) {
2037
2062
  const labeledInternalNodes = unrootedPhylo.data.filter((d) => !d.isTip && d.thisLabel);
2038
2063
  group