@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.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
- input,
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 (onNodeClick) {
1438
+ if (effectiveOnNodeClick) {
1408
1439
  internalDots
1409
1440
  .style("cursor", "pointer")
1410
- .on("click", (event, d) => onNodeClick(d, event));
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 (onNodeClick) {
1798
+ if (effectiveOnNodeClick) {
1768
1799
  internalDots
1769
1800
  .style("cursor", "pointer")
1770
- .on("click", (event, d) => onNodeClick(d, event));
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 && onNodeClick) {
2064
+ if (internalNodeCircles && effectiveOnNodeClick) {
2034
2065
  nodes
2035
2066
  .filter((d) => !d.isTip)
2036
2067
  .style("cursor", "pointer")
2037
- .on("click", (event, d) => onNodeClick(d, event));
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'."