@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.js CHANGED
@@ -1223,8 +1223,12 @@ function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize
1223
1223
  .text(opts.label ?? String(length));
1224
1224
  }
1225
1225
 
1226
+ // input: a Newick string, or an already-parsed tree (the node shape returned
1227
+ // by readTree()/randomTree()). Passing the same parsed tree object back in
1228
+ // across re-renders (e.g. after mutating it with rotate()) keeps node ids
1229
+ // stable, which onNodeClick below relies on.
1226
1230
  function drawPhylogeny(
1227
- treeText,
1231
+ input,
1228
1232
  {
1229
1233
  layout = "rect", // rect/radial/unrooted
1230
1234
  width = 800,
@@ -1242,6 +1246,7 @@ function drawPhylogeny(
1242
1246
  nodeLabelFontSize, // defaults to labelFontSize
1243
1247
  scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
1244
1248
  alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
1249
+ onNodeClick, // (node, event) => void — fires when an internal node circle is clicked (requires internalNodeCircles: true)
1245
1250
  showTooltips = true,
1246
1251
  tooltipFormatter = (d, rtt) =>
1247
1252
  `${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
@@ -1256,6 +1261,9 @@ function drawPhylogeny(
1256
1261
  // shared helpers
1257
1262
  const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
1258
1263
  const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
1264
+ const parsedTree = (input && typeof input === "object" && Array.isArray(input.children))
1265
+ ? input
1266
+ : readTree(input);
1259
1267
  // Works for both radial (uses `r`) and rect (uses `x1`).
1260
1268
  // Falls back to summing branchLength up to the root if neither is present.
1261
1269
  function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
@@ -1284,7 +1292,7 @@ function drawPhylogeny(
1284
1292
 
1285
1293
  if (layout === "rect") {
1286
1294
  // RECTANGULAR LAYOUT
1287
- const tree_df = rectangleLayout(readTree(treeText));
1295
+ const tree_df = rectangleLayout(parsedTree);
1288
1296
  const horizontal = tree_df.horizontal_lines;
1289
1297
  const vertical = tree_df.vertical_lines;
1290
1298
  const tips = horizontal.filter((d) => d.isTip);
@@ -1395,6 +1403,12 @@ function drawPhylogeny(
1395
1403
  .append("title")
1396
1404
  .text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
1397
1405
  }
1406
+
1407
+ if (onNodeClick) {
1408
+ internalDots
1409
+ .style("cursor", "pointer")
1410
+ .on("click", (event, d) => onNodeClick(d, event));
1411
+ }
1398
1412
  }
1399
1413
 
1400
1414
  // internal node labels (optional)
@@ -1530,7 +1544,6 @@ function drawPhylogeny(
1530
1544
  if (width !== height) {
1531
1545
  throw new Error("width and height must be the same for radial layout");
1532
1546
  }
1533
- const parsedTree = readTree(treeText);
1534
1547
  const rad = radialLayout(parsedTree, {
1535
1548
  angleStrategy: "fan",
1536
1549
  arcsStyle: "fan"
@@ -1750,6 +1763,12 @@ function drawPhylogeny(
1750
1763
  .append("title")
1751
1764
  .text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
1752
1765
  }
1766
+
1767
+ if (onNodeClick) {
1768
+ internalDots
1769
+ .style("cursor", "pointer")
1770
+ .on("click", (event, d) => onNodeClick(d, event));
1771
+ }
1753
1772
  }
1754
1773
 
1755
1774
  // ===== INTERNAL NODE LABELS (optional) =====
@@ -1950,7 +1969,6 @@ function drawPhylogeny(
1950
1969
  return svg.node();
1951
1970
  } else if (layout === "unrooted") {
1952
1971
  // UNROOTED LAYOUT
1953
- const parsedTree = readTree(treeText);
1954
1972
  const unrootedPhylo = unrooted(parsedTree);
1955
1973
 
1956
1974
  const w = width;
@@ -2012,6 +2030,13 @@ function drawPhylogeny(
2012
2030
  .attr("stroke-width", 2)
2013
2031
  .attr("fill", (d) => (d.isTip ? "black" : "white"));
2014
2032
 
2033
+ if (internalNodeCircles && onNodeClick) {
2034
+ nodes
2035
+ .filter((d) => !d.isTip)
2036
+ .style("cursor", "pointer")
2037
+ .on("click", (event, d) => onNodeClick(d, event));
2038
+ }
2039
+
2015
2040
  if (nodeLabels) {
2016
2041
  const labeledInternalNodes = unrootedPhylo.data.filter((d) => !d.isTip && d.thisLabel);
2017
2042
  group