@euphrasiologist/lwphylo 1.2.18 → 1.2.20
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/drawPhylogeny.cjs +51 -23
- package/dist/drawPhylogeny.cjs.map +1 -1
- package/dist/drawPhylogeny.esm.js +51 -23
- package/dist/drawPhylogeny.esm.js.map +1 -1
- package/dist/drawPhylogeny.umd.js +1 -1
- package/dist/drawPhylogeny.umd.js.map +1 -1
- package/dist/index.cjs +51 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +51 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -586,43 +586,73 @@ function getChildArcsFan(pd) {
|
|
|
586
586
|
const TAU = Math.PI * 2;
|
|
587
587
|
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
588
588
|
|
|
589
|
+
const ccwDelta = (a0, a1) => (a1 - a0 + TAU) % TAU;
|
|
590
|
+
const cwDelta = (a0, a1) => (a0 - a1 + TAU) % TAU;
|
|
591
|
+
|
|
589
592
|
// Circular midpoint that travels CCW from a -> b by half the CCW span
|
|
590
593
|
function midCCW(a, b) {
|
|
591
|
-
const d = (b - a + TAU) % TAU;
|
|
594
|
+
const d = (b - a + TAU) % TAU; // CCW delta in [0, 2π)
|
|
592
595
|
return norm(a + d / 2);
|
|
593
596
|
}
|
|
594
597
|
|
|
595
|
-
const
|
|
596
|
-
|
|
598
|
+
const key = (x) => (typeof x === "string" ? +x : x);
|
|
599
|
+
|
|
600
|
+
const byId = new Map(pd.map(d => [key(d.thisId), d]));
|
|
601
|
+
const childrenByParent = new Map(
|
|
602
|
+
pd.map(d => [
|
|
603
|
+
key(d.thisId),
|
|
604
|
+
// normalize children to numeric IDs; drop anything we can't resolve
|
|
605
|
+
(d.children || [])
|
|
606
|
+
.map(ch => (typeof ch === "object" ? ch.thisId : ch))
|
|
607
|
+
.map(key)
|
|
608
|
+
.filter(id => byId.has(id))
|
|
609
|
+
])
|
|
610
|
+
);
|
|
597
611
|
|
|
598
612
|
const child_arcs = [];
|
|
599
613
|
|
|
600
|
-
for (const
|
|
601
|
-
const
|
|
614
|
+
for (const parentRaw of pd) {
|
|
615
|
+
const pid = key(parentRaw.thisId);
|
|
616
|
+
const kids = childrenByParent.get(pid) || [];
|
|
602
617
|
if (kids.length < 2) continue;
|
|
603
618
|
|
|
604
619
|
// Sort children by angle (normalized) around the circle
|
|
605
620
|
const A = kids
|
|
606
|
-
.map(id =>
|
|
621
|
+
.map(id => {
|
|
622
|
+
const node = byId.get(id);
|
|
623
|
+
return node ? { id, a: norm(node.angle) } : null;
|
|
624
|
+
})
|
|
625
|
+
.filter(Boolean)
|
|
607
626
|
.sort((u, v) => u.a - v.a);
|
|
608
627
|
|
|
609
628
|
const N = A.length;
|
|
629
|
+
if (N < 2) continue;
|
|
630
|
+
|
|
631
|
+
const parent = byId.get(pid);
|
|
632
|
+
const radius = parent?.r;
|
|
633
|
+
if (!(radius > 0)) continue;
|
|
634
|
+
|
|
610
635
|
for (let i = 0; i < N; i++) {
|
|
611
636
|
const prev = A[(i - 1 + N) % N];
|
|
612
|
-
const cur
|
|
637
|
+
const cur = A[i];
|
|
613
638
|
const next = A[(i + 1) % N];
|
|
614
639
|
|
|
615
|
-
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
|
|
640
|
+
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next), CCW
|
|
616
641
|
const start = midCCW(prev.a, cur.a);
|
|
617
|
-
const end
|
|
642
|
+
const end = midCCW(cur.a, next.a);
|
|
643
|
+
|
|
644
|
+
// Choose the **shorter** sweep (0 = CCW, 1 = CW in our math-angle convention)
|
|
645
|
+
const ccw = ccwDelta(start, end);
|
|
646
|
+
const cw = cwDelta(start, end);
|
|
647
|
+
const sweep = ccw <= cw ? 0 : 1;
|
|
618
648
|
|
|
619
649
|
child_arcs.push({
|
|
620
|
-
parentId:
|
|
650
|
+
parentId: pid,
|
|
621
651
|
childId: cur.id,
|
|
622
|
-
radius
|
|
652
|
+
radius,
|
|
623
653
|
start,
|
|
624
654
|
end,
|
|
625
|
-
sweep
|
|
655
|
+
sweep
|
|
626
656
|
});
|
|
627
657
|
}
|
|
628
658
|
}
|
|
@@ -1598,7 +1628,7 @@ function drawPhylogeny(
|
|
|
1598
1628
|
let first = true;
|
|
1599
1629
|
while (cur && cur.parentId != null) {
|
|
1600
1630
|
// ----- spoke (parent → child) -----
|
|
1601
|
-
const s = spokeByChild.get(cur.thisId);
|
|
1631
|
+
const s = spokeByChild.get(key(cur.thisId));
|
|
1602
1632
|
if (s) {
|
|
1603
1633
|
const px = s.x0,
|
|
1604
1634
|
py = s.y0;
|
|
@@ -1622,23 +1652,21 @@ function drawPhylogeny(
|
|
|
1622
1652
|
}
|
|
1623
1653
|
|
|
1624
1654
|
// ----- half-arc at parent radius (parent.angle → child.angle) -----
|
|
1625
|
-
const a = arcByChild.get(cur.thisId);
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1655
|
+
const a = arcByChild.get(key(cur.thisId));
|
|
1656
|
+
function pathFromArcRecord(rec) {
|
|
1657
|
+
const R = radiusPx(rec.radius);
|
|
1658
|
+
return rec.sweep == null
|
|
1659
|
+
? describeArc(centerX, centerY, R, rec.start, rec.end)
|
|
1660
|
+
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep);
|
|
1661
|
+
}
|
|
1634
1662
|
|
|
1663
|
+
if (a) {
|
|
1635
1664
|
arcLayer
|
|
1636
1665
|
.append("path")
|
|
1637
1666
|
.attr("d", pathFromArcRecord(a))
|
|
1638
1667
|
.attr("fill", "none")
|
|
1639
1668
|
.attr("stroke", stroke)
|
|
1640
1669
|
.attr("stroke-width", width);
|
|
1641
|
-
|
|
1642
1670
|
}
|
|
1643
1671
|
|
|
1644
1672
|
first = false;
|