@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
|
@@ -452,43 +452,73 @@ function getChildArcsFan(pd) {
|
|
|
452
452
|
const TAU = Math.PI * 2;
|
|
453
453
|
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
454
454
|
|
|
455
|
+
const ccwDelta = (a0, a1) => (a1 - a0 + TAU) % TAU;
|
|
456
|
+
const cwDelta = (a0, a1) => (a0 - a1 + TAU) % TAU;
|
|
457
|
+
|
|
455
458
|
// Circular midpoint that travels CCW from a -> b by half the CCW span
|
|
456
459
|
function midCCW(a, b) {
|
|
457
|
-
const d = (b - a + TAU) % TAU;
|
|
460
|
+
const d = (b - a + TAU) % TAU; // CCW delta in [0, 2π)
|
|
458
461
|
return norm(a + d / 2);
|
|
459
462
|
}
|
|
460
463
|
|
|
461
|
-
const
|
|
462
|
-
|
|
464
|
+
const key = (x) => (typeof x === "string" ? +x : x);
|
|
465
|
+
|
|
466
|
+
const byId = new Map(pd.map(d => [key(d.thisId), d]));
|
|
467
|
+
const childrenByParent = new Map(
|
|
468
|
+
pd.map(d => [
|
|
469
|
+
key(d.thisId),
|
|
470
|
+
// normalize children to numeric IDs; drop anything we can't resolve
|
|
471
|
+
(d.children || [])
|
|
472
|
+
.map(ch => (typeof ch === "object" ? ch.thisId : ch))
|
|
473
|
+
.map(key)
|
|
474
|
+
.filter(id => byId.has(id))
|
|
475
|
+
])
|
|
476
|
+
);
|
|
463
477
|
|
|
464
478
|
const child_arcs = [];
|
|
465
479
|
|
|
466
|
-
for (const
|
|
467
|
-
const
|
|
480
|
+
for (const parentRaw of pd) {
|
|
481
|
+
const pid = key(parentRaw.thisId);
|
|
482
|
+
const kids = childrenByParent.get(pid) || [];
|
|
468
483
|
if (kids.length < 2) continue;
|
|
469
484
|
|
|
470
485
|
// Sort children by angle (normalized) around the circle
|
|
471
486
|
const A = kids
|
|
472
|
-
.map(id =>
|
|
487
|
+
.map(id => {
|
|
488
|
+
const node = byId.get(id);
|
|
489
|
+
return node ? { id, a: norm(node.angle) } : null;
|
|
490
|
+
})
|
|
491
|
+
.filter(Boolean)
|
|
473
492
|
.sort((u, v) => u.a - v.a);
|
|
474
493
|
|
|
475
494
|
const N = A.length;
|
|
495
|
+
if (N < 2) continue;
|
|
496
|
+
|
|
497
|
+
const parent = byId.get(pid);
|
|
498
|
+
const radius = parent?.r;
|
|
499
|
+
if (!(radius > 0)) continue;
|
|
500
|
+
|
|
476
501
|
for (let i = 0; i < N; i++) {
|
|
477
502
|
const prev = A[(i - 1 + N) % N];
|
|
478
|
-
const cur
|
|
503
|
+
const cur = A[i];
|
|
479
504
|
const next = A[(i + 1) % N];
|
|
480
505
|
|
|
481
|
-
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next)
|
|
506
|
+
// “fan” wedge around the child: midpoint(prev→cur) .. midpoint(cur→next), CCW
|
|
482
507
|
const start = midCCW(prev.a, cur.a);
|
|
483
|
-
const end
|
|
508
|
+
const end = midCCW(cur.a, next.a);
|
|
509
|
+
|
|
510
|
+
// Choose the **shorter** sweep (0 = CCW, 1 = CW in our math-angle convention)
|
|
511
|
+
const ccw = ccwDelta(start, end);
|
|
512
|
+
const cw = cwDelta(start, end);
|
|
513
|
+
const sweep = ccw <= cw ? 0 : 1;
|
|
484
514
|
|
|
485
515
|
child_arcs.push({
|
|
486
|
-
parentId:
|
|
516
|
+
parentId: pid,
|
|
487
517
|
childId: cur.id,
|
|
488
|
-
radius
|
|
518
|
+
radius,
|
|
489
519
|
start,
|
|
490
520
|
end,
|
|
491
|
-
sweep
|
|
521
|
+
sweep
|
|
492
522
|
});
|
|
493
523
|
}
|
|
494
524
|
}
|
|
@@ -1410,7 +1440,7 @@ function drawPhylogeny(
|
|
|
1410
1440
|
let first = true;
|
|
1411
1441
|
while (cur && cur.parentId != null) {
|
|
1412
1442
|
// ----- spoke (parent → child) -----
|
|
1413
|
-
const s = spokeByChild.get(cur.thisId);
|
|
1443
|
+
const s = spokeByChild.get(key(cur.thisId));
|
|
1414
1444
|
if (s) {
|
|
1415
1445
|
const px = s.x0,
|
|
1416
1446
|
py = s.y0;
|
|
@@ -1434,23 +1464,21 @@ function drawPhylogeny(
|
|
|
1434
1464
|
}
|
|
1435
1465
|
|
|
1436
1466
|
// ----- half-arc at parent radius (parent.angle → child.angle) -----
|
|
1437
|
-
const a = arcByChild.get(cur.thisId);
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1467
|
+
const a = arcByChild.get(key(cur.thisId));
|
|
1468
|
+
function pathFromArcRecord(rec) {
|
|
1469
|
+
const R = radiusPx(rec.radius);
|
|
1470
|
+
return rec.sweep == null
|
|
1471
|
+
? describeArc(centerX, centerY, R, rec.start, rec.end)
|
|
1472
|
+
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep);
|
|
1473
|
+
}
|
|
1446
1474
|
|
|
1475
|
+
if (a) {
|
|
1447
1476
|
arcLayer
|
|
1448
1477
|
.append("path")
|
|
1449
1478
|
.attr("d", pathFromArcRecord(a))
|
|
1450
1479
|
.attr("fill", "none")
|
|
1451
1480
|
.attr("stroke", stroke)
|
|
1452
1481
|
.attr("stroke-width", width);
|
|
1453
|
-
|
|
1454
1482
|
}
|
|
1455
1483
|
|
|
1456
1484
|
first = false;
|