@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/drawPhylogeny.cjs
CHANGED
|
@@ -473,43 +473,73 @@ function getChildArcsFan(pd) {
|
|
|
473
473
|
const TAU = Math.PI * 2;
|
|
474
474
|
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
475
475
|
|
|
476
|
+
const ccwDelta = (a0, a1) => (a1 - a0 + TAU) % TAU;
|
|
477
|
+
const cwDelta = (a0, a1) => (a0 - a1 + TAU) % TAU;
|
|
478
|
+
|
|
476
479
|
// Circular midpoint that travels CCW from a -> b by half the CCW span
|
|
477
480
|
function midCCW(a, b) {
|
|
478
|
-
const d = (b - a + TAU) % TAU;
|
|
481
|
+
const d = (b - a + TAU) % TAU; // CCW delta in [0, 2π)
|
|
479
482
|
return norm(a + d / 2);
|
|
480
483
|
}
|
|
481
484
|
|
|
482
|
-
const
|
|
483
|
-
|
|
485
|
+
const key = (x) => (typeof x === "string" ? +x : x);
|
|
486
|
+
|
|
487
|
+
const byId = new Map(pd.map(d => [key(d.thisId), d]));
|
|
488
|
+
const childrenByParent = new Map(
|
|
489
|
+
pd.map(d => [
|
|
490
|
+
key(d.thisId),
|
|
491
|
+
// normalize children to numeric IDs; drop anything we can't resolve
|
|
492
|
+
(d.children || [])
|
|
493
|
+
.map(ch => (typeof ch === "object" ? ch.thisId : ch))
|
|
494
|
+
.map(key)
|
|
495
|
+
.filter(id => byId.has(id))
|
|
496
|
+
])
|
|
497
|
+
);
|
|
484
498
|
|
|
485
499
|
const child_arcs = [];
|
|
486
500
|
|
|
487
|
-
for (const
|
|
488
|
-
const
|
|
501
|
+
for (const parentRaw of pd) {
|
|
502
|
+
const pid = key(parentRaw.thisId);
|
|
503
|
+
const kids = childrenByParent.get(pid) || [];
|
|
489
504
|
if (kids.length < 2) continue;
|
|
490
505
|
|
|
491
506
|
// Sort children by angle (normalized) around the circle
|
|
492
507
|
const A = kids
|
|
493
|
-
.map(id =>
|
|
508
|
+
.map(id => {
|
|
509
|
+
const node = byId.get(id);
|
|
510
|
+
return node ? { id, a: norm(node.angle) } : null;
|
|
511
|
+
})
|
|
512
|
+
.filter(Boolean)
|
|
494
513
|
.sort((u, v) => u.a - v.a);
|
|
495
514
|
|
|
496
515
|
const N = A.length;
|
|
516
|
+
if (N < 2) continue;
|
|
517
|
+
|
|
518
|
+
const parent = byId.get(pid);
|
|
519
|
+
const radius = parent?.r;
|
|
520
|
+
if (!(radius > 0)) continue;
|
|
521
|
+
|
|
497
522
|
for (let i = 0; i < N; i++) {
|
|
498
523
|
const prev = A[(i - 1 + N) % N];
|
|
499
|
-
const cur
|
|
524
|
+
const cur = A[i];
|
|
500
525
|
const next = A[(i + 1) % N];
|
|
501
526
|
|
|
502
|
-
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
|
|
527
|
+
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next), CCW
|
|
503
528
|
const start = midCCW(prev.a, cur.a);
|
|
504
|
-
const end
|
|
529
|
+
const end = midCCW(cur.a, next.a);
|
|
530
|
+
|
|
531
|
+
// Choose the **shorter** sweep (0 = CCW, 1 = CW in our math-angle convention)
|
|
532
|
+
const ccw = ccwDelta(start, end);
|
|
533
|
+
const cw = cwDelta(start, end);
|
|
534
|
+
const sweep = ccw <= cw ? 0 : 1;
|
|
505
535
|
|
|
506
536
|
child_arcs.push({
|
|
507
|
-
parentId:
|
|
537
|
+
parentId: pid,
|
|
508
538
|
childId: cur.id,
|
|
509
|
-
radius
|
|
539
|
+
radius,
|
|
510
540
|
start,
|
|
511
541
|
end,
|
|
512
|
-
sweep
|
|
542
|
+
sweep
|
|
513
543
|
});
|
|
514
544
|
}
|
|
515
545
|
}
|
|
@@ -1431,7 +1461,7 @@ function drawPhylogeny(
|
|
|
1431
1461
|
let first = true;
|
|
1432
1462
|
while (cur && cur.parentId != null) {
|
|
1433
1463
|
// ----- spoke (parent → child) -----
|
|
1434
|
-
const s = spokeByChild.get(cur.thisId);
|
|
1464
|
+
const s = spokeByChild.get(key(cur.thisId));
|
|
1435
1465
|
if (s) {
|
|
1436
1466
|
const px = s.x0,
|
|
1437
1467
|
py = s.y0;
|
|
@@ -1455,23 +1485,21 @@ function drawPhylogeny(
|
|
|
1455
1485
|
}
|
|
1456
1486
|
|
|
1457
1487
|
// ----- half-arc at parent radius (parent.angle → child.angle) -----
|
|
1458
|
-
const a = arcByChild.get(cur.thisId);
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1488
|
+
const a = arcByChild.get(key(cur.thisId));
|
|
1489
|
+
function pathFromArcRecord(rec) {
|
|
1490
|
+
const R = radiusPx(rec.radius);
|
|
1491
|
+
return rec.sweep == null
|
|
1492
|
+
? describeArc(centerX, centerY, R, rec.start, rec.end)
|
|
1493
|
+
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep);
|
|
1494
|
+
}
|
|
1467
1495
|
|
|
1496
|
+
if (a) {
|
|
1468
1497
|
arcLayer
|
|
1469
1498
|
.append("path")
|
|
1470
1499
|
.attr("d", pathFromArcRecord(a))
|
|
1471
1500
|
.attr("fill", "none")
|
|
1472
1501
|
.attr("stroke", stroke)
|
|
1473
1502
|
.attr("stroke-width", width);
|
|
1474
|
-
|
|
1475
1503
|
}
|
|
1476
1504
|
|
|
1477
1505
|
first = false;
|