@euphrasiologist/lwphylo 1.2.24 → 1.3.0
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/README.md +5 -6
- package/dist/drawPhylogeny.cjs +66 -132
- package/dist/drawPhylogeny.cjs.map +1 -1
- package/dist/drawPhylogeny.esm.js +66 -132
- 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 +146 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +146 -133
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -144,8 +144,8 @@ function describeArc(cx, cy, radius, startAngle, endAngle) {
|
|
|
144
144
|
return `M ${p.x} ${p.y}`; // degenerate span → no arc
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const largeArcFlag = delta > Math.PI ? 1 : 0;
|
|
148
|
-
const sweepFlag = 0; // CCW
|
|
147
|
+
const largeArcFlag = delta > Math.PI ? 1 : 0;
|
|
148
|
+
const sweepFlag = 0; // CCW in our y-flipped coords: math-CCW = decreasing SVG angle = sweepFlag 0
|
|
149
149
|
|
|
150
150
|
const p0 = polarToCartesian(cx, cy, radius, a0);
|
|
151
151
|
const p1 = polarToCartesian(cx, cy, radius, a1);
|
|
@@ -154,15 +154,17 @@ function describeArc(cx, cy, radius, startAngle, endAngle) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// src/radial/describeArcSweep.js
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
// IMPORTANT: angles are in "math space" (increasing = CCW).
|
|
158
|
+
// Because we map y as (cy - r*sin(a)), our math angle t maps to SVG angle -t.
|
|
159
|
+
// Increasing t (math CCW) = decreasing SVG angle = sweepFlag 0 (negative direction).
|
|
160
|
+
// math CCW -> svg sweepFlag = 0
|
|
161
|
+
// math CW -> svg sweepFlag = 1
|
|
162
|
+
function describeArcSweep(
|
|
163
|
+
cx, cy, r,
|
|
164
|
+
a0, a1,
|
|
165
|
+
mathSweep = "ccw", // "ccw" | "cw"
|
|
166
|
+
largeArcFlag = 0
|
|
167
|
+
) {
|
|
166
168
|
if (!(r > 0)) return "";
|
|
167
169
|
|
|
168
170
|
const x0 = cx + r * Math.cos(a0);
|
|
@@ -170,7 +172,9 @@ function describeArcSweep(cx, cy, r, a0, a1, sweep = 1, largeArcFlag = 0) {
|
|
|
170
172
|
const x1 = cx + r * Math.cos(a1);
|
|
171
173
|
const y1 = cy - r * Math.sin(a1);
|
|
172
174
|
|
|
173
|
-
|
|
175
|
+
const svgSweepFlag = (mathSweep === "ccw") ? 0 : 1;
|
|
176
|
+
|
|
177
|
+
return `M ${x0} ${y0} A ${r} ${r} 0 ${largeArcFlag} ${svgSweepFlag} ${x1} ${y1}`;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
/**
|
|
@@ -447,16 +451,13 @@ function getArcs(pd) {
|
|
|
447
451
|
}
|
|
448
452
|
|
|
449
453
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* start = first child's angle
|
|
453
|
-
* end = last child's angle
|
|
454
|
-
* sweep = 0 (CCW) if end>=start; 1 (CW) if wrapped across 2π
|
|
455
|
-
*
|
|
456
|
-
* @param {Array} pd nodes with {thisId,parentId,children,angle,r}
|
|
457
|
-
* @returns {Array} [{parentId,thisId,radius,start,end,sweep}]
|
|
454
|
+
* APE-like block arcs per internal parent.
|
|
455
|
+
* Draw CCW from first child's angle to last child's angle (wrapping allowed).
|
|
458
456
|
*/
|
|
459
457
|
function getArcsFan(pd) {
|
|
458
|
+
const TAU = Math.PI * 2;
|
|
459
|
+
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
460
|
+
|
|
460
461
|
const byId = new Map(pd.map(d => [d.thisId, d]));
|
|
461
462
|
const arcs = [];
|
|
462
463
|
|
|
@@ -468,9 +469,11 @@ function getArcsFan(pd) {
|
|
|
468
469
|
const last = byId.get(c[c.length - 1])?.angle;
|
|
469
470
|
if (first == null || last == null) continue;
|
|
470
471
|
|
|
471
|
-
const start = first;
|
|
472
|
-
const end
|
|
473
|
-
|
|
472
|
+
const start = norm(first);
|
|
473
|
+
const end = norm(last);
|
|
474
|
+
|
|
475
|
+
const deltaCCW = (end - start + TAU) % TAU;
|
|
476
|
+
if (deltaCCW < 1e-9) continue;
|
|
474
477
|
|
|
475
478
|
arcs.push({
|
|
476
479
|
parentId: p.parentId,
|
|
@@ -478,9 +481,11 @@ function getArcsFan(pd) {
|
|
|
478
481
|
radius: p.r,
|
|
479
482
|
start,
|
|
480
483
|
end,
|
|
481
|
-
sweep
|
|
484
|
+
sweep: "ccw", // << math sweep
|
|
485
|
+
largeArc: deltaCCW > Math.PI ? 1 : 0
|
|
482
486
|
});
|
|
483
487
|
}
|
|
488
|
+
|
|
484
489
|
return arcs;
|
|
485
490
|
}
|
|
486
491
|
|
|
@@ -566,76 +571,6 @@ function getChildArcs(pd) {
|
|
|
566
571
|
return arcs;
|
|
567
572
|
}
|
|
568
573
|
|
|
569
|
-
function getChildArcsFan(pd) {
|
|
570
|
-
const TAU = Math.PI * 2;
|
|
571
|
-
const norm = (t) => ((t % TAU) + TAU) % TAU;
|
|
572
|
-
|
|
573
|
-
function midCCW(a, b) {
|
|
574
|
-
const d = (b - a + TAU) % TAU;
|
|
575
|
-
return norm(a + d / 2);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const key = (x) => (typeof x === "string" ? +x : x);
|
|
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
|
-
(d.children || [])
|
|
584
|
-
.map(ch => (typeof ch === "object" ? ch.thisId : ch))
|
|
585
|
-
.map(key)
|
|
586
|
-
.filter(id => byId.has(id))
|
|
587
|
-
])
|
|
588
|
-
);
|
|
589
|
-
|
|
590
|
-
const child_arcs = [];
|
|
591
|
-
|
|
592
|
-
for (const parentRaw of pd) {
|
|
593
|
-
const pid = key(parentRaw.thisId);
|
|
594
|
-
const kids = childrenByParent.get(pid) || [];
|
|
595
|
-
if (kids.length < 2) continue;
|
|
596
|
-
|
|
597
|
-
const A = kids
|
|
598
|
-
.map(id => {
|
|
599
|
-
const node = byId.get(id);
|
|
600
|
-
return node ? { id, a: norm(node.angle) } : null;
|
|
601
|
-
})
|
|
602
|
-
.filter(Boolean)
|
|
603
|
-
.sort((u, v) => u.a - v.a);
|
|
604
|
-
|
|
605
|
-
const N = A.length;
|
|
606
|
-
if (N < 2) continue;
|
|
607
|
-
|
|
608
|
-
const parent = byId.get(pid);
|
|
609
|
-
const radius = parent?.r;
|
|
610
|
-
if (!(radius > 0)) continue;
|
|
611
|
-
|
|
612
|
-
for (let i = 0; i < N; i++) {
|
|
613
|
-
const prev = A[(i - 1 + N) % N];
|
|
614
|
-
const cur = A[i];
|
|
615
|
-
const next = A[(i + 1) % N];
|
|
616
|
-
|
|
617
|
-
const start = midCCW(prev.a, cur.a);
|
|
618
|
-
const end = midCCW(cur.a, next.a);
|
|
619
|
-
|
|
620
|
-
const sweep = 1; // always clockwise
|
|
621
|
-
const delta = (end - start + TAU) % TAU;
|
|
622
|
-
const largeArc = delta > Math.PI ? 1 : 0;
|
|
623
|
-
|
|
624
|
-
child_arcs.push({
|
|
625
|
-
parentId: pid,
|
|
626
|
-
childId: cur.id,
|
|
627
|
-
radius,
|
|
628
|
-
start,
|
|
629
|
-
end,
|
|
630
|
-
sweep,
|
|
631
|
-
largeArc
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
return child_arcs;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
574
|
/**
|
|
640
575
|
* radialLayout(node, opts?)
|
|
641
576
|
* opts:
|
|
@@ -679,13 +614,8 @@ function radialLayout(node, opts = {}) {
|
|
|
679
614
|
? getArcsFan(pd)
|
|
680
615
|
: getArcs(pd);
|
|
681
616
|
|
|
682
|
-
// per-child arcs for
|
|
683
|
-
|
|
684
|
-
if (arcsStyle === "fan") {
|
|
685
|
-
child_arcs = getChildArcsFan(pd);
|
|
686
|
-
} else {
|
|
687
|
-
child_arcs = getChildArcs(pd);
|
|
688
|
-
}
|
|
617
|
+
// per-child arcs for path highlighting: always parent.angle → child.angle at parent.r
|
|
618
|
+
const child_arcs = getChildArcs(pd);
|
|
689
619
|
|
|
690
620
|
return { data: pd, radii, arcs, child_arcs };
|
|
691
621
|
}
|
|
@@ -1008,6 +938,85 @@ function parentFisheye(d, data) {
|
|
|
1008
938
|
return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
|
|
1009
939
|
}
|
|
1010
940
|
|
|
941
|
+
/**
|
|
942
|
+
* Generate a random bifurcating tree with `nTips` tips, in the same
|
|
943
|
+
* parent/children node shape produced by readTree().
|
|
944
|
+
*
|
|
945
|
+
* Topology is grown by repeatedly picking a random extant lineage to split
|
|
946
|
+
* (a Yule/coalescent-style process), so internal branching order is random
|
|
947
|
+
* rather than a fixed balanced/caterpillar shape. Branch lengths are drawn
|
|
948
|
+
* uniformly from [0, maxBranchLength).
|
|
949
|
+
*/
|
|
950
|
+
|
|
951
|
+
function randomTree(nTips = 10, {
|
|
952
|
+
maxBranchLength = 1,
|
|
953
|
+
labelPrefix = 't',
|
|
954
|
+
seed = null
|
|
955
|
+
} = {}) {
|
|
956
|
+
if (!Number.isInteger(nTips) || nTips < 1) {
|
|
957
|
+
throw new Error("nTips must be a positive integer");
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const random = seed == null ? Math.random : mulberry32(seed);
|
|
961
|
+
|
|
962
|
+
let nodeId = 0;
|
|
963
|
+
const makeNode = (parent) => ({
|
|
964
|
+
parent,
|
|
965
|
+
children: [],
|
|
966
|
+
id: nodeId++,
|
|
967
|
+
label: '',
|
|
968
|
+
branchLength: null
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
const root = makeNode(null);
|
|
972
|
+
|
|
973
|
+
if (nTips === 1) {
|
|
974
|
+
root.label = `${labelPrefix}1`;
|
|
975
|
+
return root;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// start with two lineages hanging off the root
|
|
979
|
+
let lineages = [makeNode(root), makeNode(root)];
|
|
980
|
+
root.children.push(...lineages);
|
|
981
|
+
|
|
982
|
+
// repeatedly split a random lineage until we have nTips of them
|
|
983
|
+
while (lineages.length < nTips) {
|
|
984
|
+
const i = Math.floor(random() * lineages.length);
|
|
985
|
+
const parent = lineages[i];
|
|
986
|
+
const left = makeNode(parent);
|
|
987
|
+
const right = makeNode(parent);
|
|
988
|
+
parent.children.push(left, right);
|
|
989
|
+
lineages.splice(i, 1, left, right);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// assign branch lengths to every non-root node, and tip labels in
|
|
993
|
+
// left-to-right order
|
|
994
|
+
let tipIndex = 0;
|
|
995
|
+
const assign = (node) => {
|
|
996
|
+
for (const child of node.children) {
|
|
997
|
+
child.branchLength = random() * maxBranchLength;
|
|
998
|
+
assign(child);
|
|
999
|
+
}
|
|
1000
|
+
if (node.children.length === 0) {
|
|
1001
|
+
node.label = `${labelPrefix}${++tipIndex}`;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
assign(root);
|
|
1005
|
+
|
|
1006
|
+
return root;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// small deterministic PRNG so `seed` gives reproducible trees
|
|
1010
|
+
function mulberry32(seed) {
|
|
1011
|
+
let a = seed >>> 0;
|
|
1012
|
+
return function () {
|
|
1013
|
+
a |= 0; a = (a + 0x6D2B79F5) | 0;
|
|
1014
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
1015
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
1016
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1011
1020
|
/**
|
|
1012
1021
|
* Parse a Newick tree string into a doubly-linked list of JS Objects.
|
|
1013
1022
|
* Assigns labels, branch lengths, and node IDs (tips before internals if input emits them that way).
|
|
@@ -1022,21 +1031,29 @@ function readTree(text) {
|
|
|
1022
1031
|
text = String(text).replace(/\s+/g, '');
|
|
1023
1032
|
|
|
1024
1033
|
const tokens = text.split(/(;|\(|\)|,)/);
|
|
1025
|
-
const root = { parent: null, children: [] };
|
|
1026
|
-
let curnode = root;
|
|
1027
1034
|
let nodeId = 0;
|
|
1035
|
+
const makeNode = (parent) => ({
|
|
1036
|
+
parent,
|
|
1037
|
+
children: [],
|
|
1038
|
+
id: nodeId++,
|
|
1039
|
+
label: '',
|
|
1040
|
+
branchLength: null
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const root = makeNode(null);
|
|
1044
|
+
let curnode = root;
|
|
1028
1045
|
|
|
1029
1046
|
for (const token of tokens) {
|
|
1030
1047
|
if (!token || token === ';') continue;
|
|
1031
1048
|
|
|
1032
1049
|
if (token === '(') {
|
|
1033
|
-
const child =
|
|
1050
|
+
const child = makeNode(curnode);
|
|
1034
1051
|
curnode.children.push(child);
|
|
1035
1052
|
curnode = child; // descend
|
|
1036
1053
|
} else if (token === ',') {
|
|
1037
1054
|
// back to parent, then create sibling
|
|
1038
1055
|
curnode = curnode.parent;
|
|
1039
|
-
const child =
|
|
1056
|
+
const child = makeNode(curnode);
|
|
1040
1057
|
curnode.children.push(child);
|
|
1041
1058
|
curnode = child;
|
|
1042
1059
|
} else if (token === ')') {
|
|
@@ -1045,14 +1062,15 @@ function readTree(text) {
|
|
|
1045
1062
|
if (curnode === null) break;
|
|
1046
1063
|
} else {
|
|
1047
1064
|
// label/branch-length chunk (e.g., "A:0.01" or "A")
|
|
1065
|
+
// Note: nodes are assigned an id at creation (above), so internal
|
|
1066
|
+
// (clade) nodes that carry neither a label nor a branch length —
|
|
1067
|
+
// e.g. "((A,B),(C,D));" — still get a valid, linkable id here.
|
|
1048
1068
|
const nodeinfo = token.split(':');
|
|
1049
1069
|
if (nodeinfo.length === 1) {
|
|
1050
1070
|
if (token.startsWith(':')) {
|
|
1051
|
-
curnode.label = '';
|
|
1052
1071
|
curnode.branchLength = parseFloat(nodeinfo[0]);
|
|
1053
1072
|
} else {
|
|
1054
1073
|
curnode.label = nodeinfo[0];
|
|
1055
|
-
curnode.branchLength = null;
|
|
1056
1074
|
}
|
|
1057
1075
|
} else if (nodeinfo.length === 2) {
|
|
1058
1076
|
curnode.label = nodeinfo[0];
|
|
@@ -1062,13 +1080,9 @@ function readTree(text) {
|
|
|
1062
1080
|
curnode.label = nodeinfo[0] || '';
|
|
1063
1081
|
curnode.branchLength = parseFloat(nodeinfo[nodeinfo.length - 1]);
|
|
1064
1082
|
}
|
|
1065
|
-
curnode.id = nodeId++; // assign then increment
|
|
1066
1083
|
}
|
|
1067
1084
|
}
|
|
1068
1085
|
|
|
1069
|
-
// Ensure root has an id if not assigned during parsing
|
|
1070
|
-
if (root.id == null) root.id = nodeId;
|
|
1071
|
-
|
|
1072
1086
|
return root;
|
|
1073
1087
|
}
|
|
1074
1088
|
|
|
@@ -1128,6 +1142,7 @@ function drawPhylogeny(
|
|
|
1128
1142
|
strokeWidth = 1, // for the phylogeny branches
|
|
1129
1143
|
radialMode = "outer", // "outer" (co-circular tips) or "phylo" (true terminals)
|
|
1130
1144
|
tipLabels = true,
|
|
1145
|
+
labelFontSize = 10, // font size (px) for tip labels
|
|
1131
1146
|
showTooltips = true,
|
|
1132
1147
|
tooltipFormatter = (d, rtt) =>
|
|
1133
1148
|
`${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
|
|
@@ -1175,7 +1190,7 @@ function drawPhylogeny(
|
|
|
1175
1190
|
const tips = horizontal.filter((d) => d.isTip);
|
|
1176
1191
|
|
|
1177
1192
|
// indices & root→tip getter
|
|
1178
|
-
const byId = new Map(
|
|
1193
|
+
const byId = new Map(tree_df.data.map((d) => [d.thisId, d])); // includes root
|
|
1179
1194
|
const tipById = new Map(tips.map((d) => [d.thisId, d]));
|
|
1180
1195
|
const tipByLabel = new Map(tips.map((d) => [d.thisLabel, d]));
|
|
1181
1196
|
const rootToTip = makeRootToTipGetter(byId, { prefer: "x1" });
|
|
@@ -1249,6 +1264,7 @@ function drawPhylogeny(
|
|
|
1249
1264
|
// interactive root→tip highlight (rect) on dot hover
|
|
1250
1265
|
tipDots
|
|
1251
1266
|
.on("mouseenter", function(_event, d) {
|
|
1267
|
+
hoverLayer.selectAll("*").remove();
|
|
1252
1268
|
drawRectPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1253
1269
|
d3.select(this).attr("r", 4);
|
|
1254
1270
|
})
|
|
@@ -1268,7 +1284,7 @@ function drawPhylogeny(
|
|
|
1268
1284
|
.attr("x", (d) => xScale(d.x1) + 4)
|
|
1269
1285
|
.attr("y", (d) => yScale(d.y1))
|
|
1270
1286
|
.attr("dy", "0.32em")
|
|
1271
|
-
.attr("font-size",
|
|
1287
|
+
.attr("font-size", labelFontSize)
|
|
1272
1288
|
.text((d) => d.thisLabel?.replace(/_/g, " ") ?? "");
|
|
1273
1289
|
|
|
1274
1290
|
if (showTooltips) {
|
|
@@ -1279,6 +1295,7 @@ function drawPhylogeny(
|
|
|
1279
1295
|
|
|
1280
1296
|
labels
|
|
1281
1297
|
.on("mouseenter", function(_event, d) {
|
|
1298
|
+
hoverLayer.selectAll("*").remove();
|
|
1282
1299
|
drawRectPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1283
1300
|
d3.select(this).attr("font-weight", 600);
|
|
1284
1301
|
})
|
|
@@ -1305,7 +1322,6 @@ function drawPhylogeny(
|
|
|
1305
1322
|
|
|
1306
1323
|
// helper to draw root→tip for rect (both vertical+horizontal)
|
|
1307
1324
|
function drawRectPath(tipId, layer, stroke, width) {
|
|
1308
|
-
layer.selectAll("*").remove();
|
|
1309
1325
|
let cur = byId.get(tipId);
|
|
1310
1326
|
while (cur && cur.parentId != null) {
|
|
1311
1327
|
const parent = byId.get(cur.parentId);
|
|
@@ -1362,10 +1378,14 @@ function drawPhylogeny(
|
|
|
1362
1378
|
const END_CAP = 0;
|
|
1363
1379
|
|
|
1364
1380
|
// ===== SCALES / BOUNDS =====
|
|
1365
|
-
const maxRadius = d3.max(rad.data, (d) => d.r) ?? 0;
|
|
1366
|
-
const scaleRadial = maxRadius + 2 * radialMargin;
|
|
1367
1381
|
const w = width,
|
|
1368
1382
|
h = height;
|
|
1383
|
+
const maxRadius = d3.max(rad.data, (d) => d.r) ?? 0;
|
|
1384
|
+
// radialMargin is in pixels: tips sit (radialMargin) px from the SVG edge.
|
|
1385
|
+
// Derive the data-space scale so that radiusPx(maxRadius) = w/2 - radialMargin.
|
|
1386
|
+
const scaleRadial = maxRadius > 0
|
|
1387
|
+
? maxRadius * (w / 2) / (w / 2 - radialMargin)
|
|
1388
|
+
: 1;
|
|
1369
1389
|
const centerX = w / 2,
|
|
1370
1390
|
centerY = h / 2;
|
|
1371
1391
|
|
|
@@ -1445,8 +1465,8 @@ function drawPhylogeny(
|
|
|
1445
1465
|
radiusPx(d.radius),
|
|
1446
1466
|
d.start,
|
|
1447
1467
|
d.end,
|
|
1448
|
-
d.sweep,
|
|
1449
|
-
d.largeArc,
|
|
1468
|
+
d.sweep ?? "ccw",
|
|
1469
|
+
d.largeArc ?? 0,
|
|
1450
1470
|
)
|
|
1451
1471
|
)
|
|
1452
1472
|
.attr("fill", "none")
|
|
@@ -1561,7 +1581,7 @@ function drawPhylogeny(
|
|
|
1561
1581
|
.attr("x", xoff)
|
|
1562
1582
|
.attr("alignment-baseline", "middle")
|
|
1563
1583
|
.attr("text-anchor", anchor)
|
|
1564
|
-
.attr("font-size",
|
|
1584
|
+
.attr("font-size", labelFontSize)
|
|
1565
1585
|
.attr("fill", "black")
|
|
1566
1586
|
.text((d) => d.thisLabel?.replace(/_/g, " ") ?? "");
|
|
1567
1587
|
});
|
|
@@ -1575,6 +1595,8 @@ function drawPhylogeny(
|
|
|
1575
1595
|
// label hover
|
|
1576
1596
|
labels
|
|
1577
1597
|
.on("mouseenter", function(_event, d) {
|
|
1598
|
+
hoverLines.selectAll("*").remove();
|
|
1599
|
+
hoverArcs.selectAll("*").remove();
|
|
1578
1600
|
drawRadialPath(d, hoverLines, hoverArcs, hoverStroke, hoverWidth);
|
|
1579
1601
|
d3.select(this).select("text").attr("font-weight", 600);
|
|
1580
1602
|
})
|
|
@@ -1594,9 +1616,6 @@ function drawPhylogeny(
|
|
|
1594
1616
|
width = 3
|
|
1595
1617
|
) {
|
|
1596
1618
|
// target may be a tip node *or* a numeric tip id
|
|
1597
|
-
lineLayer.selectAll("*").remove();
|
|
1598
|
-
arcLayer.selectAll("*").remove();
|
|
1599
|
-
|
|
1600
1619
|
let cur = (typeof target === "number" || typeof target === "string")
|
|
1601
1620
|
? byId.get(target)
|
|
1602
1621
|
: target;
|
|
@@ -1635,18 +1654,10 @@ function drawPhylogeny(
|
|
|
1635
1654
|
const R = radiusPx(rec.radius);
|
|
1636
1655
|
return rec.sweep == null
|
|
1637
1656
|
? describeArc(centerX, centerY, R, rec.start, rec.end)
|
|
1638
|
-
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep,
|
|
1657
|
+
: describeArcSweep(centerX, centerY, R, rec.start, rec.end, rec.sweep ?? "ccw", rec.largeArc ?? 0);
|
|
1639
1658
|
}
|
|
1640
1659
|
|
|
1641
1660
|
if (a) {
|
|
1642
|
-
console.log("Drawing arc:", {
|
|
1643
|
-
childId: cur.thisId,
|
|
1644
|
-
startDeg: (a.start * 180 / Math.PI).toFixed(2),
|
|
1645
|
-
endDeg: (a.end * 180 / Math.PI).toFixed(2),
|
|
1646
|
-
sweep: a.sweep,
|
|
1647
|
-
radius: a.radius
|
|
1648
|
-
});
|
|
1649
|
-
|
|
1650
1661
|
arcLayer
|
|
1651
1662
|
.append("path")
|
|
1652
1663
|
.attr("d", pathFromArcRecord(a))
|
|
@@ -1663,6 +1674,8 @@ function drawPhylogeny(
|
|
|
1663
1674
|
// tip dot hover
|
|
1664
1675
|
tipDots
|
|
1665
1676
|
.on("mouseenter", function(_event, d) {
|
|
1677
|
+
hoverLines.selectAll("*").remove();
|
|
1678
|
+
hoverArcs.selectAll("*").remove();
|
|
1666
1679
|
drawRadialPath(d, hoverLines, hoverArcs, hoverStroke, hoverWidth);
|
|
1667
1680
|
d3.select(this).attr("r", DOT_R + 2);
|
|
1668
1681
|
})
|
|
@@ -1819,7 +1832,7 @@ function drawPhylogeny(
|
|
|
1819
1832
|
.attr("x", xOffset)
|
|
1820
1833
|
.attr("alignment-baseline", "middle")
|
|
1821
1834
|
.attr("text-anchor", anchor)
|
|
1822
|
-
.attr("font-size",
|
|
1835
|
+
.attr("font-size", labelFontSize)
|
|
1823
1836
|
.attr("fill", "black")
|
|
1824
1837
|
.text(d.thisLabel?.replace(/_/g, " ") ?? "");
|
|
1825
1838
|
});
|
|
@@ -1900,5 +1913,5 @@ function drawPhylogeny(
|
|
|
1900
1913
|
}
|
|
1901
1914
|
}
|
|
1902
1915
|
|
|
1903
|
-
export { describeArc, describeArcSweep, drawPhylogeny, parentFisheye, phisheye, polarToCartesian, radialLayout, readTree, rectangleLayout, subTree, unrooted };
|
|
1916
|
+
export { describeArc, describeArcSweep, drawPhylogeny, parentFisheye, phisheye, polarToCartesian, radialLayout, randomTree, readTree, rectangleLayout, subTree, unrooted };
|
|
1904
1917
|
//# sourceMappingURL=index.js.map
|