@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.js
CHANGED
|
@@ -565,43 +565,73 @@ function getChildArcsFan(pd) {
|
|
|
565
565
|
const TAU = Math.PI * 2;
|
|
566
566
|
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
567
567
|
|
|
568
|
+
const ccwDelta = (a0, a1) => (a1 - a0 + TAU) % TAU;
|
|
569
|
+
const cwDelta = (a0, a1) => (a0 - a1 + TAU) % TAU;
|
|
570
|
+
|
|
568
571
|
// Circular midpoint that travels CCW from a -> b by half the CCW span
|
|
569
572
|
function midCCW(a, b) {
|
|
570
|
-
const d = (b - a + TAU) % TAU;
|
|
573
|
+
const d = (b - a + TAU) % TAU; // CCW delta in [0, 2π)
|
|
571
574
|
return norm(a + d / 2);
|
|
572
575
|
}
|
|
573
576
|
|
|
574
|
-
const
|
|
575
|
-
|
|
577
|
+
const key = (x) => (typeof x === "string" ? +x : x);
|
|
578
|
+
|
|
579
|
+
const byId = new Map(pd.map(d => [key(d.thisId), d]));
|
|
580
|
+
const childrenByParent = new Map(
|
|
581
|
+
pd.map(d => [
|
|
582
|
+
key(d.thisId),
|
|
583
|
+
// normalize children to numeric IDs; drop anything we can't resolve
|
|
584
|
+
(d.children || [])
|
|
585
|
+
.map(ch => (typeof ch === "object" ? ch.thisId : ch))
|
|
586
|
+
.map(key)
|
|
587
|
+
.filter(id => byId.has(id))
|
|
588
|
+
])
|
|
589
|
+
);
|
|
576
590
|
|
|
577
591
|
const child_arcs = [];
|
|
578
592
|
|
|
579
|
-
for (const
|
|
580
|
-
const
|
|
593
|
+
for (const parentRaw of pd) {
|
|
594
|
+
const pid = key(parentRaw.thisId);
|
|
595
|
+
const kids = childrenByParent.get(pid) || [];
|
|
581
596
|
if (kids.length < 2) continue;
|
|
582
597
|
|
|
583
598
|
// Sort children by angle (normalized) around the circle
|
|
584
599
|
const A = kids
|
|
585
|
-
.map(id =>
|
|
600
|
+
.map(id => {
|
|
601
|
+
const node = byId.get(id);
|
|
602
|
+
return node ? { id, a: norm(node.angle) } : null;
|
|
603
|
+
})
|
|
604
|
+
.filter(Boolean)
|
|
586
605
|
.sort((u, v) => u.a - v.a);
|
|
587
606
|
|
|
588
607
|
const N = A.length;
|
|
608
|
+
if (N < 2) continue;
|
|
609
|
+
|
|
610
|
+
const parent = byId.get(pid);
|
|
611
|
+
const radius = parent?.r;
|
|
612
|
+
if (!(radius > 0)) continue;
|
|
613
|
+
|
|
589
614
|
for (let i = 0; i < N; i++) {
|
|
590
615
|
const prev = A[(i - 1 + N) % N];
|
|
591
|
-
const cur
|
|
616
|
+
const cur = A[i];
|
|
592
617
|
const next = A[(i + 1) % N];
|
|
593
618
|
|
|
594
|
-
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
|
|
619
|
+
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next), CCW
|
|
595
620
|
const start = midCCW(prev.a, cur.a);
|
|
596
|
-
const end
|
|
621
|
+
const end = midCCW(cur.a, next.a);
|
|
622
|
+
|
|
623
|
+
// Choose the **shorter** sweep (0 = CCW, 1 = CW in our math-angle convention)
|
|
624
|
+
const ccw = ccwDelta(start, end);
|
|
625
|
+
const cw = cwDelta(start, end);
|
|
626
|
+
const sweep = ccw <= cw ? 0 : 1;
|
|
597
627
|
|
|
598
628
|
child_arcs.push({
|
|
599
|
-
parentId:
|
|
629
|
+
parentId: pid,
|
|
600
630
|
childId: cur.id,
|
|
601
|
-
radius
|
|
631
|
+
radius,
|
|
602
632
|
start,
|
|
603
633
|
end,
|
|
604
|
-
sweep
|
|
634
|
+
sweep
|
|
605
635
|
});
|
|
606
636
|
}
|
|
607
637
|
}
|
|
@@ -1577,7 +1607,7 @@ function drawPhylogeny(
|
|
|
1577
1607
|
let first = true;
|
|
1578
1608
|
while (cur && cur.parentId != null) {
|
|
1579
1609
|
// ----- spoke (parent → child) -----
|
|
1580
|
-
const s = spokeByChild.get(cur.thisId);
|
|
1610
|
+
const s = spokeByChild.get(key(cur.thisId));
|
|
1581
1611
|
if (s) {
|
|
1582
1612
|
const px = s.x0,
|
|
1583
1613
|
py = s.y0;
|
|
@@ -1601,23 +1631,21 @@ function drawPhylogeny(
|
|
|
1601
1631
|
}
|
|
1602
1632
|
|
|
1603
1633
|
// ----- half-arc at parent radius (parent.angle → child.angle) -----
|
|
1604
|
-
const a = arcByChild.get(cur.thisId);
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1634
|
+
const a = arcByChild.get(key(cur.thisId));
|
|
1635
|
+
function pathFromArcRecord(rec) {
|
|
1636
|
+
const R = radiusPx(rec.radius);
|
|
1637
|
+
return rec.sweep == null
|
|
1638
|
+
? describeArc(centerX, centerY, R, rec.start, rec.end)
|
|
1639
|
+
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep);
|
|
1640
|
+
}
|
|
1613
1641
|
|
|
1642
|
+
if (a) {
|
|
1614
1643
|
arcLayer
|
|
1615
1644
|
.append("path")
|
|
1616
1645
|
.attr("d", pathFromArcRecord(a))
|
|
1617
1646
|
.attr("fill", "none")
|
|
1618
1647
|
.attr("stroke", stroke)
|
|
1619
1648
|
.attr("stroke-width", width);
|
|
1620
|
-
|
|
1621
1649
|
}
|
|
1622
1650
|
|
|
1623
1651
|
first = false;
|