@euphrasiologist/lwphylo 1.2.12 → 1.2.15

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
@@ -561,6 +561,54 @@ function getChildArcs(pd) {
561
561
  return arcs;
562
562
  }
563
563
 
564
+ function getChildArcsFan(pd) {
565
+ const TAU = Math.PI * 2;
566
+ const norm = (t) => ((t % TAU) + TAU) % TAU;
567
+
568
+ // Circular midpoint that travels CCW from a -> b by half the CCW span
569
+ function midCCW(a, b) {
570
+ const d = (b - a + TAU) % TAU; // CCW delta a->b in [0, 2π)
571
+ return norm(a + d / 2);
572
+ }
573
+
574
+ const byId = new Map(pd.map(d => [d.thisId, d]));
575
+ const childrenByParent = new Map(pd.map(d => [d.thisId, d.children || []]));
576
+
577
+ const child_arcs = [];
578
+
579
+ for (const parent of pd) {
580
+ const kids = childrenByParent.get(parent.thisId) || [];
581
+ if (kids.length < 2) continue;
582
+
583
+ // Sort children by angle (normalized) around the circle
584
+ const A = kids
585
+ .map(id => ({ id, a: norm(byId.get(id).angle) }))
586
+ .sort((u, v) => u.a - v.a);
587
+
588
+ const N = A.length;
589
+ for (let i = 0; i < N; i++) {
590
+ const prev = A[(i - 1 + N) % N];
591
+ const cur = A[i];
592
+ const next = A[(i + 1) % N];
593
+
594
+ // “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
595
+ const start = midCCW(prev.a, cur.a);
596
+ const end = midCCW(cur.a, next.a);
597
+
598
+ child_arcs.push({
599
+ parentId: parent.thisId,
600
+ childId: cur.id,
601
+ radius: parent.r, // IMPORTANT: draw at the PARENT circle
602
+ start,
603
+ end,
604
+ sweep: 0 // CCW (matches describeArcSweep usage)
605
+ });
606
+ }
607
+ }
608
+
609
+ return child_arcs;
610
+ }
611
+
564
612
  // src/radial/radialLayout.js
565
613
 
566
614
  /**
@@ -607,7 +655,12 @@ function radialLayout(node, opts = {}) {
607
655
  : getArcs(pd);
608
656
 
609
657
  // per-child arcs for half-arc highlighting if you already use them
610
- const child_arcs = getChildArcs(pd);
658
+ let child_arcs = [];
659
+ if (opts.arcsStyle === "fan") {
660
+ child_arcs = getChildArcsFan(pd);
661
+ } else {
662
+ child_arcs = getChildArcs(pd);
663
+ }
611
664
 
612
665
  return { data: pd, radii, arcs, child_arcs };
613
666
  }
@@ -1261,6 +1314,10 @@ function drawPhylogeny(
1261
1314
 
1262
1315
  return svg.node();
1263
1316
  } else if (layout === "radial") {
1317
+ // RADIAL LAYOUT
1318
+ if (width !== height) {
1319
+ throw new Error("width and height must be the same for radial layout");
1320
+ }
1264
1321
  const parsedTree = readTree(treeText);
1265
1322
  const rad = radialLayout(parsedTree, {
1266
1323
  angleStrategy: "fan",
@@ -1270,6 +1327,10 @@ function drawPhylogeny(
1270
1327
  // ===== MODE =====
1271
1328
  const TIP_MODE = radialMode; // "phylo" (shorten to original tips) or "outer" (project to one circle)
1272
1329
  const isOuter = TIP_MODE === "outer";
1330
+ if (TIP_MODE !== "phylo" && TIP_MODE !== "outer") {
1331
+ throw new Error("radialMode must be either 'phylo' or 'outer'");
1332
+ }
1333
+
1273
1334
 
1274
1335
  // visuals (0 = let spokes reach the dots)
1275
1336
  const DOT_R = 3;
@@ -1362,7 +1423,6 @@ function drawPhylogeny(
1362
1423
  d.sweep
1363
1424
  )
1364
1425
  )
1365
-
1366
1426
  .attr("fill", "none")
1367
1427
  .attr("stroke", "#777")
1368
1428
  .attr("stroke-width", strokeWidth);
@@ -1435,7 +1495,7 @@ function drawPhylogeny(
1435
1495
 
1436
1496
  // maps for fast lookup on hover (childId → spoke / arc)
1437
1497
  const spokeByChild = new Map(rad.radii.map((s) => [childIdOf(s), s]));
1438
- const arcByChild = new Map(rad.child_arcs.map((a) => [a.childId, a]));
1498
+ const arcByChild = new Map((rad.child_arcs ?? []).map((a) => [a.childId, a]));
1439
1499
 
1440
1500
  // ===== LABELS =====
1441
1501
  // Labels — make them follow the tip position used by the current mode
@@ -1541,29 +1601,17 @@ function drawPhylogeny(
1541
1601
  // ----- half-arc at parent radius (parent.angle → child.angle) -----
1542
1602
  const a = arcByChild.get(cur.thisId);
1543
1603
  if (a) {
1604
+ const pathD = (a.sweep == null)
1605
+ ? describeArc(centerX, centerY, radiusPx(a.radius), a.start, a.end)
1606
+ : describeArcSweep(centerX, centerY, radiusPx(a.radius), a.start, a.end, a.sweep);
1607
+
1544
1608
  arcLayer
1545
1609
  .append("path")
1546
- .attr("d", (d) =>
1547
- d.sweep == null
1548
- ? describeArc(
1549
- centerX,
1550
- centerY,
1551
- radiusPx(d.radius),
1552
- d.start,
1553
- d.end
1554
- )
1555
- : describeArcSweep(
1556
- centerX,
1557
- centerY,
1558
- radiusPx(d.radius),
1559
- d.start,
1560
- d.end,
1561
- d.sweep
1562
- )
1563
- )
1610
+ .attr("d", pathD)
1564
1611
  .attr("fill", "none")
1565
1612
  .attr("stroke", stroke)
1566
1613
  .attr("stroke-width", width);
1614
+
1567
1615
  }
1568
1616
 
1569
1617
  first = false;