@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.cjs CHANGED
@@ -582,6 +582,54 @@ function getChildArcs(pd) {
582
582
  return arcs;
583
583
  }
584
584
 
585
+ function getChildArcsFan(pd) {
586
+ const TAU = Math.PI * 2;
587
+ const norm = (t) => ((t % TAU) + TAU) % TAU;
588
+
589
+ // Circular midpoint that travels CCW from a -> b by half the CCW span
590
+ function midCCW(a, b) {
591
+ const d = (b - a + TAU) % TAU; // CCW delta a->b in [0, 2π)
592
+ return norm(a + d / 2);
593
+ }
594
+
595
+ const byId = new Map(pd.map(d => [d.thisId, d]));
596
+ const childrenByParent = new Map(pd.map(d => [d.thisId, d.children || []]));
597
+
598
+ const child_arcs = [];
599
+
600
+ for (const parent of pd) {
601
+ const kids = childrenByParent.get(parent.thisId) || [];
602
+ if (kids.length < 2) continue;
603
+
604
+ // Sort children by angle (normalized) around the circle
605
+ const A = kids
606
+ .map(id => ({ id, a: norm(byId.get(id).angle) }))
607
+ .sort((u, v) => u.a - v.a);
608
+
609
+ const N = A.length;
610
+ for (let i = 0; i < N; i++) {
611
+ const prev = A[(i - 1 + N) % N];
612
+ const cur = A[i];
613
+ const next = A[(i + 1) % N];
614
+
615
+ // “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
616
+ const start = midCCW(prev.a, cur.a);
617
+ const end = midCCW(cur.a, next.a);
618
+
619
+ child_arcs.push({
620
+ parentId: parent.thisId,
621
+ childId: cur.id,
622
+ radius: parent.r, // IMPORTANT: draw at the PARENT circle
623
+ start,
624
+ end,
625
+ sweep: 0 // CCW (matches describeArcSweep usage)
626
+ });
627
+ }
628
+ }
629
+
630
+ return child_arcs;
631
+ }
632
+
585
633
  // src/radial/radialLayout.js
586
634
 
587
635
  /**
@@ -628,7 +676,12 @@ function radialLayout(node, opts = {}) {
628
676
  : getArcs(pd);
629
677
 
630
678
  // per-child arcs for half-arc highlighting if you already use them
631
- const child_arcs = getChildArcs(pd);
679
+ let child_arcs = [];
680
+ if (opts.arcsStyle === "fan") {
681
+ child_arcs = getChildArcsFan(pd);
682
+ } else {
683
+ child_arcs = getChildArcs(pd);
684
+ }
632
685
 
633
686
  return { data: pd, radii, arcs, child_arcs };
634
687
  }
@@ -1282,6 +1335,10 @@ function drawPhylogeny(
1282
1335
 
1283
1336
  return svg.node();
1284
1337
  } else if (layout === "radial") {
1338
+ // RADIAL LAYOUT
1339
+ if (width !== height) {
1340
+ throw new Error("width and height must be the same for radial layout");
1341
+ }
1285
1342
  const parsedTree = readTree(treeText);
1286
1343
  const rad = radialLayout(parsedTree, {
1287
1344
  angleStrategy: "fan",
@@ -1291,6 +1348,10 @@ function drawPhylogeny(
1291
1348
  // ===== MODE =====
1292
1349
  const TIP_MODE = radialMode; // "phylo" (shorten to original tips) or "outer" (project to one circle)
1293
1350
  const isOuter = TIP_MODE === "outer";
1351
+ if (TIP_MODE !== "phylo" && TIP_MODE !== "outer") {
1352
+ throw new Error("radialMode must be either 'phylo' or 'outer'");
1353
+ }
1354
+
1294
1355
 
1295
1356
  // visuals (0 = let spokes reach the dots)
1296
1357
  const DOT_R = 3;
@@ -1383,7 +1444,6 @@ function drawPhylogeny(
1383
1444
  d.sweep
1384
1445
  )
1385
1446
  )
1386
-
1387
1447
  .attr("fill", "none")
1388
1448
  .attr("stroke", "#777")
1389
1449
  .attr("stroke-width", strokeWidth);
@@ -1456,7 +1516,7 @@ function drawPhylogeny(
1456
1516
 
1457
1517
  // maps for fast lookup on hover (childId → spoke / arc)
1458
1518
  const spokeByChild = new Map(rad.radii.map((s) => [childIdOf(s), s]));
1459
- const arcByChild = new Map(rad.child_arcs.map((a) => [a.childId, a]));
1519
+ const arcByChild = new Map((rad.child_arcs ?? []).map((a) => [a.childId, a]));
1460
1520
 
1461
1521
  // ===== LABELS =====
1462
1522
  // Labels — make them follow the tip position used by the current mode
@@ -1562,29 +1622,17 @@ function drawPhylogeny(
1562
1622
  // ----- half-arc at parent radius (parent.angle → child.angle) -----
1563
1623
  const a = arcByChild.get(cur.thisId);
1564
1624
  if (a) {
1625
+ const pathD = (a.sweep == null)
1626
+ ? describeArc(centerX, centerY, radiusPx(a.radius), a.start, a.end)
1627
+ : describeArcSweep(centerX, centerY, radiusPx(a.radius), a.start, a.end, a.sweep);
1628
+
1565
1629
  arcLayer
1566
1630
  .append("path")
1567
- .attr("d", (d) =>
1568
- d.sweep == null
1569
- ? describeArc(
1570
- centerX,
1571
- centerY,
1572
- radiusPx(d.radius),
1573
- d.start,
1574
- d.end
1575
- )
1576
- : describeArcSweep(
1577
- centerX,
1578
- centerY,
1579
- radiusPx(d.radius),
1580
- d.start,
1581
- d.end,
1582
- d.sweep
1583
- )
1584
- )
1631
+ .attr("d", pathD)
1585
1632
  .attr("fill", "none")
1586
1633
  .attr("stroke", stroke)
1587
1634
  .attr("stroke-width", width);
1635
+
1588
1636
  }
1589
1637
 
1590
1638
  first = false;