@euphrasiologist/lwphylo 1.3.0 → 1.4.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 +10 -1
- package/dist/drawPhylogeny.cjs +226 -9
- package/dist/drawPhylogeny.cjs.map +1 -1
- package/dist/drawPhylogeny.esm.js +226 -9
- 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 +281 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +279 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -950,6 +950,23 @@ function unrooted (node) {
|
|
|
950
950
|
return data;
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
/**
|
|
954
|
+
* Reorder every node's children by descendant tip count, so the tree
|
|
955
|
+
* "ladders" from smallest to largest clade at each branching point
|
|
956
|
+
* (or the reverse, with ascending: false). Mutates the tree in place.
|
|
957
|
+
*/
|
|
958
|
+
function ladderize(tree, { ascending = true } = {}) {
|
|
959
|
+
const sign = ascending ? 1 : -1;
|
|
960
|
+
|
|
961
|
+
(function sort(node) {
|
|
962
|
+
if (node.children.length === 0) return;
|
|
963
|
+
node.children.forEach(sort);
|
|
964
|
+
node.children.sort((a, b) => sign * (numTips(a) - numTips(b)));
|
|
965
|
+
})(tree);
|
|
966
|
+
|
|
967
|
+
return tree;
|
|
968
|
+
}
|
|
969
|
+
|
|
953
970
|
function makeIndexById(rows, key = "thisId") {
|
|
954
971
|
return new Map(rows.map(d => [d[key], d]));
|
|
955
972
|
}
|
|
@@ -959,6 +976,20 @@ function parentFisheye(d, data) {
|
|
|
959
976
|
return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
|
|
960
977
|
}
|
|
961
978
|
|
|
979
|
+
/**
|
|
980
|
+
* Reverse the child order at one node, flipping which side of the tree
|
|
981
|
+
* that clade's descendants are drawn on. Defaults to the root. Mutates
|
|
982
|
+
* the tree in place.
|
|
983
|
+
*/
|
|
984
|
+
function rotate(tree, nodeId = tree.id) {
|
|
985
|
+
const target = preorder(tree).find((n) => n.id === nodeId);
|
|
986
|
+
if (!target) {
|
|
987
|
+
throw new Error(`No node with id ${nodeId} found in tree`);
|
|
988
|
+
}
|
|
989
|
+
target.children.reverse();
|
|
990
|
+
return tree;
|
|
991
|
+
}
|
|
992
|
+
|
|
962
993
|
/**
|
|
963
994
|
* Generate a random bifurcating tree with `nTips` tips, in the same
|
|
964
995
|
* parent/children node shape produced by readTree().
|
|
@@ -1152,6 +1183,67 @@ function subTree (tree, node) {
|
|
|
1152
1183
|
return Object.fromEntries(data.concat(filtered));
|
|
1153
1184
|
}
|
|
1154
1185
|
|
|
1186
|
+
/**
|
|
1187
|
+
* Serialize a parsed tree (the parent/children node shape produced by
|
|
1188
|
+
* readTree() and randomTree()) back into a Newick string.
|
|
1189
|
+
*/
|
|
1190
|
+
|
|
1191
|
+
function toNewick(node) {
|
|
1192
|
+
return `${serialize(node)};`;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function serialize(node) {
|
|
1196
|
+
const label = node.label || '';
|
|
1197
|
+
const branch = node.branchLength == null ? '' : `:${node.branchLength}`;
|
|
1198
|
+
|
|
1199
|
+
if (node.children.length === 0) {
|
|
1200
|
+
return `${label}${branch}`;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const children = node.children.map(serialize).join(',');
|
|
1204
|
+
return `(${children})${label}${branch}`;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Round a raw data-space length to a "nice" 1/2/5-of-a-power-of-ten value,
|
|
1208
|
+
// so an auto-sized scale bar doesn't show an ugly number like "0.347".
|
|
1209
|
+
function niceScaleLength(target) {
|
|
1210
|
+
if (!(target > 0)) return 1;
|
|
1211
|
+
const exp = Math.floor(Math.log10(target));
|
|
1212
|
+
const base = Math.pow(10, exp);
|
|
1213
|
+
const residual = target / base;
|
|
1214
|
+
const niceResidual = residual < 1.5 ? 1 : residual < 3.5 ? 2 : residual < 7.5 ? 5 : 10;
|
|
1215
|
+
return niceResidual * base;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// scaleBar: true | number (explicit data-units length) | { length, x, y, label }
|
|
1219
|
+
function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize }) {
|
|
1220
|
+
const opts = (scaleBar === true || typeof scaleBar === "number") ? {} : scaleBar;
|
|
1221
|
+
const length = typeof scaleBar === "number" ? scaleBar : (opts.length ?? niceScaleLength(basis / 5));
|
|
1222
|
+
const x = opts.x ?? defaultX;
|
|
1223
|
+
const y = opts.y ?? defaultY;
|
|
1224
|
+
const barPx = scale(length) - scale(0);
|
|
1225
|
+
|
|
1226
|
+
const g = svg.append("g").attr("class", "phylo_scale_bar");
|
|
1227
|
+
g.append("line")
|
|
1228
|
+
.attr("x1", x).attr("x2", x + barPx)
|
|
1229
|
+
.attr("y1", y).attr("y2", y)
|
|
1230
|
+
.attr("stroke", "#000")
|
|
1231
|
+
.attr("stroke-width", 1);
|
|
1232
|
+
[x, x + barPx].forEach((tx) => {
|
|
1233
|
+
g.append("line")
|
|
1234
|
+
.attr("x1", tx).attr("x2", tx)
|
|
1235
|
+
.attr("y1", y - 4).attr("y2", y + 4)
|
|
1236
|
+
.attr("stroke", "#000")
|
|
1237
|
+
.attr("stroke-width", 1);
|
|
1238
|
+
});
|
|
1239
|
+
g.append("text")
|
|
1240
|
+
.attr("x", x + barPx / 2)
|
|
1241
|
+
.attr("y", y - 6)
|
|
1242
|
+
.attr("text-anchor", "middle")
|
|
1243
|
+
.attr("font-size", fontSize)
|
|
1244
|
+
.text(opts.label ?? String(length));
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1155
1247
|
function drawPhylogeny(
|
|
1156
1248
|
treeText,
|
|
1157
1249
|
{
|
|
@@ -1164,6 +1256,13 @@ function drawPhylogeny(
|
|
|
1164
1256
|
radialMode = "outer", // "outer" (co-circular tips) or "phylo" (true terminals)
|
|
1165
1257
|
tipLabels = true,
|
|
1166
1258
|
labelFontSize = 10, // font size (px) for tip labels
|
|
1259
|
+
tipRadius, // px radius of tip dots; defaults to each layout's original size
|
|
1260
|
+
internalNodeCircles = false, // draw a circle at every internal (non-tip) node
|
|
1261
|
+
internalNodeRadius = 3, // px radius for internal node circles
|
|
1262
|
+
nodeLabels = false, // draw text labels at internal nodes (e.g. clade/support labels)
|
|
1263
|
+
nodeLabelFontSize, // defaults to labelFontSize
|
|
1264
|
+
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
1265
|
+
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
1167
1266
|
showTooltips = true,
|
|
1168
1267
|
tooltipFormatter = (d, rtt) =>
|
|
1169
1268
|
`${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
|
|
@@ -1177,6 +1276,7 @@ function drawPhylogeny(
|
|
|
1177
1276
|
|
|
1178
1277
|
// shared helpers
|
|
1179
1278
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
1279
|
+
const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
|
|
1180
1280
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
1181
1281
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
1182
1282
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1215,6 +1315,7 @@ function drawPhylogeny(
|
|
|
1215
1315
|
const tipById = new Map(tips.map((d) => [d.thisId, d]));
|
|
1216
1316
|
const tipByLabel = new Map(tips.map((d) => [d.thisLabel, d]));
|
|
1217
1317
|
const rootToTip = makeRootToTipGetter(byId, { prefer: "x1" });
|
|
1318
|
+
const R_TIP = tipRadius ?? 2;
|
|
1218
1319
|
|
|
1219
1320
|
const maxY = d3__namespace.max(horizontal, (d) => d.y1);
|
|
1220
1321
|
const minY = d3__namespace.min(horizontal, (d) => d.y1);
|
|
@@ -1272,7 +1373,7 @@ function drawPhylogeny(
|
|
|
1272
1373
|
.join("circle")
|
|
1273
1374
|
.attr("cx", (d) => xScale(d.x1))
|
|
1274
1375
|
.attr("cy", (d) => yScale(d.y1))
|
|
1275
|
-
.attr("r",
|
|
1376
|
+
.attr("r", R_TIP)
|
|
1276
1377
|
.attr("fill", "black");
|
|
1277
1378
|
|
|
1278
1379
|
// tooltips for rect dots
|
|
@@ -1287,13 +1388,72 @@ function drawPhylogeny(
|
|
|
1287
1388
|
.on("mouseenter", function(_event, d) {
|
|
1288
1389
|
hoverLayer.selectAll("*").remove();
|
|
1289
1390
|
drawRectPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1290
|
-
d3__namespace.select(this).attr("r",
|
|
1391
|
+
d3__namespace.select(this).attr("r", R_TIP + 2);
|
|
1291
1392
|
})
|
|
1292
1393
|
.on("mouseleave", function() {
|
|
1293
1394
|
hoverLayer.selectAll("*").remove();
|
|
1294
|
-
d3__namespace.select(this).attr("r",
|
|
1395
|
+
d3__namespace.select(this).attr("r", R_TIP);
|
|
1295
1396
|
});
|
|
1296
1397
|
|
|
1398
|
+
// internal node circles (optional)
|
|
1399
|
+
if (internalNodeCircles) {
|
|
1400
|
+
const internalNodes = tree_df.data.filter((d) => !d.isTip);
|
|
1401
|
+
const internalDots = group
|
|
1402
|
+
.append("g")
|
|
1403
|
+
.attr("class", "phylo_internal_dots")
|
|
1404
|
+
.selectAll("circle")
|
|
1405
|
+
.data(internalNodes)
|
|
1406
|
+
.join("circle")
|
|
1407
|
+
.attr("cx", (d) => xScale(d.x1))
|
|
1408
|
+
.attr("cy", (d) => yScale(d.y1))
|
|
1409
|
+
.attr("r", internalNodeRadius)
|
|
1410
|
+
.attr("fill", "white")
|
|
1411
|
+
.attr("stroke", "#555")
|
|
1412
|
+
.attr("stroke-width", 1);
|
|
1413
|
+
|
|
1414
|
+
if (showTooltips) {
|
|
1415
|
+
internalDots
|
|
1416
|
+
.append("title")
|
|
1417
|
+
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// internal node labels (optional)
|
|
1422
|
+
if (nodeLabels) {
|
|
1423
|
+
const labeledInternalNodes = tree_df.data.filter((d) => !d.isTip && d.thisLabel);
|
|
1424
|
+
svg
|
|
1425
|
+
.append("g")
|
|
1426
|
+
.attr("class", "phylo_node_labels")
|
|
1427
|
+
.selectAll("text")
|
|
1428
|
+
.data(labeledInternalNodes)
|
|
1429
|
+
.join("text")
|
|
1430
|
+
.attr("x", (d) => xScale(d.x1) - 4)
|
|
1431
|
+
.attr("y", (d) => yScale(d.y1) - 4)
|
|
1432
|
+
.attr("text-anchor", "end")
|
|
1433
|
+
.attr("font-size", nodeLabelSize)
|
|
1434
|
+
.text((d) => d.thisLabel);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// column that tip labels align to when alignTipLabels is set
|
|
1438
|
+
const alignX = xScale(maxX);
|
|
1439
|
+
|
|
1440
|
+
// dashed guide lines from each tip's true branch end to the aligned label column
|
|
1441
|
+
if (tipLabels && alignTipLabels) {
|
|
1442
|
+
group
|
|
1443
|
+
.append("g")
|
|
1444
|
+
.attr("class", "phylo_align_guides")
|
|
1445
|
+
.selectAll("line")
|
|
1446
|
+
.data(tips)
|
|
1447
|
+
.join("line")
|
|
1448
|
+
.attr("x1", (d) => xScale(d.x1))
|
|
1449
|
+
.attr("x2", alignX)
|
|
1450
|
+
.attr("y1", (d) => yScale(d.y1))
|
|
1451
|
+
.attr("y2", (d) => yScale(d.y1))
|
|
1452
|
+
.attr("stroke", "#999")
|
|
1453
|
+
.attr("stroke-width", 1)
|
|
1454
|
+
.attr("stroke-dasharray", "2,2");
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1297
1457
|
// labels
|
|
1298
1458
|
if (tipLabels) {
|
|
1299
1459
|
const labels = svg
|
|
@@ -1302,7 +1462,7 @@ function drawPhylogeny(
|
|
|
1302
1462
|
.selectAll("text")
|
|
1303
1463
|
.data(tips)
|
|
1304
1464
|
.join("text")
|
|
1305
|
-
.attr("x", (d) => xScale(d.x1) + 4)
|
|
1465
|
+
.attr("x", (d) => (alignTipLabels ? alignX : xScale(d.x1)) + 4)
|
|
1306
1466
|
.attr("y", (d) => yScale(d.y1))
|
|
1307
1467
|
.attr("dy", "0.32em")
|
|
1308
1468
|
.attr("font-size", labelFontSize)
|
|
@@ -1374,6 +1534,17 @@ function drawPhylogeny(
|
|
|
1374
1534
|
}
|
|
1375
1535
|
}
|
|
1376
1536
|
|
|
1537
|
+
if (scaleBar) {
|
|
1538
|
+
addScaleBar(svg, {
|
|
1539
|
+
scale: xScale,
|
|
1540
|
+
basis: maxX,
|
|
1541
|
+
defaultX: margin.left,
|
|
1542
|
+
defaultY: height - margin.bottom / 2,
|
|
1543
|
+
scaleBar,
|
|
1544
|
+
fontSize: labelFontSize
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1377
1548
|
return svg.node();
|
|
1378
1549
|
} else if (layout === "radial") {
|
|
1379
1550
|
// RADIAL LAYOUT
|
|
@@ -1395,7 +1566,7 @@ function drawPhylogeny(
|
|
|
1395
1566
|
|
|
1396
1567
|
|
|
1397
1568
|
// visuals (0 = let spokes reach the dots)
|
|
1398
|
-
const DOT_R = 3;
|
|
1569
|
+
const DOT_R = tipRadius ?? 3;
|
|
1399
1570
|
const END_CAP = 0;
|
|
1400
1571
|
|
|
1401
1572
|
// ===== SCALES / BOUNDS =====
|
|
@@ -1533,6 +1704,25 @@ function drawPhylogeny(
|
|
|
1533
1704
|
.attr("stroke-width", strokeWidth);
|
|
1534
1705
|
});
|
|
1535
1706
|
|
|
1707
|
+
// ===== ALIGN GUIDES (optional) =====
|
|
1708
|
+
// dashed lines from each tip's true position to the common label ring,
|
|
1709
|
+
// for radialMode "phylo" (true terminals) where tips aren't already co-circular
|
|
1710
|
+
if (tipLabels && alignTipLabels && !isOuter) {
|
|
1711
|
+
group
|
|
1712
|
+
.append("g")
|
|
1713
|
+
.attr("class", "phylo_align_guides")
|
|
1714
|
+
.selectAll("line")
|
|
1715
|
+
.data(tips)
|
|
1716
|
+
.join("line")
|
|
1717
|
+
.attr("x1", (d) => xScaleRadial(d.x))
|
|
1718
|
+
.attr("y1", (d) => yScaleRadial(d.y))
|
|
1719
|
+
.attr("x2", (d) => xScaleRadial(tipMaxR * Math.cos(d.angle)))
|
|
1720
|
+
.attr("y2", (d) => yScaleRadial(tipMaxR * Math.sin(d.angle)))
|
|
1721
|
+
.attr("stroke", "#999")
|
|
1722
|
+
.attr("stroke-width", 1)
|
|
1723
|
+
.attr("stroke-dasharray", "2,2");
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1536
1726
|
// ===== TIP DOTS =====
|
|
1537
1727
|
const tipDots = group
|
|
1538
1728
|
.append("g")
|
|
@@ -1560,6 +1750,45 @@ function drawPhylogeny(
|
|
|
1560
1750
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1561
1751
|
}
|
|
1562
1752
|
|
|
1753
|
+
// ===== INTERNAL NODE CIRCLES (optional) =====
|
|
1754
|
+
if (internalNodeCircles) {
|
|
1755
|
+
const internalNodes = rad.data.filter((d) => !d.isTip);
|
|
1756
|
+
const internalDots = group
|
|
1757
|
+
.append("g")
|
|
1758
|
+
.attr("class", "phylo_internal_dots")
|
|
1759
|
+
.selectAll("circle")
|
|
1760
|
+
.data(internalNodes)
|
|
1761
|
+
.join("circle")
|
|
1762
|
+
.attr("cx", (d) => xScaleRadial(d.x))
|
|
1763
|
+
.attr("cy", (d) => yScaleRadial(d.y))
|
|
1764
|
+
.attr("r", internalNodeRadius)
|
|
1765
|
+
.attr("fill", "white")
|
|
1766
|
+
.attr("stroke", "#555")
|
|
1767
|
+
.attr("stroke-width", 1);
|
|
1768
|
+
|
|
1769
|
+
if (showTooltips) {
|
|
1770
|
+
internalDots
|
|
1771
|
+
.append("title")
|
|
1772
|
+
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
// ===== INTERNAL NODE LABELS (optional) =====
|
|
1777
|
+
if (nodeLabels) {
|
|
1778
|
+
const labeledInternalNodes = rad.data.filter((d) => !d.isTip && d.thisLabel);
|
|
1779
|
+
group
|
|
1780
|
+
.append("g")
|
|
1781
|
+
.attr("class", "phylo_node_labels")
|
|
1782
|
+
.selectAll("text")
|
|
1783
|
+
.data(labeledInternalNodes)
|
|
1784
|
+
.join("text")
|
|
1785
|
+
.attr("x", (d) => xScaleRadial(d.x) + 4)
|
|
1786
|
+
.attr("y", (d) => yScaleRadial(d.y) - 4)
|
|
1787
|
+
.attr("font-size", nodeLabelSize)
|
|
1788
|
+
.attr("fill", "black")
|
|
1789
|
+
.text((d) => d.thisLabel);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1563
1792
|
// maps for fast lookup on hover (childId → spoke / arc)
|
|
1564
1793
|
const key = (x) => (typeof x === "string" ? +x : x);
|
|
1565
1794
|
const spokeByChild = new Map(rad.radii.map(s => [key(s.childId ?? s.thisId ?? s.id1), s]));
|
|
@@ -1580,7 +1809,8 @@ function drawPhylogeny(
|
|
|
1580
1809
|
// same tip position rule as dots/spokes:
|
|
1581
1810
|
// - "outer": snap to common ring (tipMaxR)
|
|
1582
1811
|
// - otherwise (e.g. "align"/"phylo"): true tip radius
|
|
1583
|
-
|
|
1812
|
+
// - alignTipLabels forces the common ring regardless of mode
|
|
1813
|
+
const r = (isOuter || alignTipLabels) ? tipMaxR : d.r;
|
|
1584
1814
|
const x = r * Math.cos(d.angle);
|
|
1585
1815
|
const y = r * Math.sin(d.angle);
|
|
1586
1816
|
return `translate(${xScaleRadial(x)},${yScaleRadial(y)})`;
|
|
@@ -1727,6 +1957,17 @@ function drawPhylogeny(
|
|
|
1727
1957
|
});
|
|
1728
1958
|
}
|
|
1729
1959
|
|
|
1960
|
+
if (scaleBar) {
|
|
1961
|
+
addScaleBar(svg, {
|
|
1962
|
+
scale: xScaleRadial,
|
|
1963
|
+
basis: maxRadius,
|
|
1964
|
+
defaultX: 20,
|
|
1965
|
+
defaultY: h - 20,
|
|
1966
|
+
scaleBar,
|
|
1967
|
+
fontSize: labelFontSize
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1730
1971
|
return svg.node();
|
|
1731
1972
|
} else if (layout === "unrooted") {
|
|
1732
1973
|
// UNROOTED LAYOUT
|
|
@@ -1776,6 +2017,8 @@ function drawPhylogeny(
|
|
|
1776
2017
|
.attr("stroke-width", strokeWidth)
|
|
1777
2018
|
.attr("stroke", "#777");
|
|
1778
2019
|
|
|
2020
|
+
const R_TIP = tipRadius ?? 4;
|
|
2021
|
+
|
|
1779
2022
|
const nodes = group
|
|
1780
2023
|
.append("g")
|
|
1781
2024
|
.attr("class", "phylo_points")
|
|
@@ -1783,13 +2026,28 @@ function drawPhylogeny(
|
|
|
1783
2026
|
.data(unrootedPhylo.data)
|
|
1784
2027
|
.join("circle")
|
|
1785
2028
|
.attr("class", "dot")
|
|
1786
|
-
.attr("r", (d) => (d.isTip ?
|
|
2029
|
+
.attr("r", (d) => (d.isTip ? R_TIP : (internalNodeCircles ? internalNodeRadius : 0)))
|
|
1787
2030
|
.attr("cx", (d) => xScaleUnroot(d.x))
|
|
1788
2031
|
.attr("cy", (d) => yScaleUnroot(d.y))
|
|
1789
2032
|
.attr("stroke", "black")
|
|
1790
2033
|
.attr("stroke-width", 2)
|
|
1791
2034
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
1792
2035
|
|
|
2036
|
+
if (nodeLabels) {
|
|
2037
|
+
const labeledInternalNodes = unrootedPhylo.data.filter((d) => !d.isTip && d.thisLabel);
|
|
2038
|
+
group
|
|
2039
|
+
.append("g")
|
|
2040
|
+
.attr("class", "phylo_node_labels")
|
|
2041
|
+
.selectAll("text")
|
|
2042
|
+
.data(labeledInternalNodes)
|
|
2043
|
+
.join("text")
|
|
2044
|
+
.attr("x", (d) => xScaleUnroot(d.x) + 4)
|
|
2045
|
+
.attr("y", (d) => yScaleUnroot(d.y) - 4)
|
|
2046
|
+
.attr("font-size", nodeLabelSize)
|
|
2047
|
+
.attr("fill", "black")
|
|
2048
|
+
.text((d) => d.thisLabel);
|
|
2049
|
+
}
|
|
2050
|
+
|
|
1793
2051
|
const byId = new Map(unrootedPhylo.data.map((d) => [d.thisId, d]));
|
|
1794
2052
|
const tipById = new Map(
|
|
1795
2053
|
unrootedPhylo.data.filter((d) => d.isTip).map((d) => [d.thisId, d])
|
|
@@ -1879,11 +2137,11 @@ function drawPhylogeny(
|
|
|
1879
2137
|
.filter((d) => d.isTip)
|
|
1880
2138
|
.on("mouseenter", function(_event, d) {
|
|
1881
2139
|
drawUnrootedPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1882
|
-
d3__namespace.select(this).attr("r",
|
|
2140
|
+
d3__namespace.select(this).attr("r", R_TIP + 2);
|
|
1883
2141
|
})
|
|
1884
2142
|
.on("mouseleave", function() {
|
|
1885
2143
|
hoverLayer.selectAll("*").remove();
|
|
1886
|
-
d3__namespace.select(this).attr("r",
|
|
2144
|
+
d3__namespace.select(this).attr("r", R_TIP);
|
|
1887
2145
|
});
|
|
1888
2146
|
|
|
1889
2147
|
if (highlightTips && highlightTips.length) {
|
|
@@ -1926,6 +2184,17 @@ function drawPhylogeny(
|
|
|
1926
2184
|
}
|
|
1927
2185
|
}
|
|
1928
2186
|
|
|
2187
|
+
if (scaleBar) {
|
|
2188
|
+
addScaleBar(svg, {
|
|
2189
|
+
scale: xScaleUnroot,
|
|
2190
|
+
basis: maxRadius,
|
|
2191
|
+
defaultX: 20,
|
|
2192
|
+
defaultY: h - 20,
|
|
2193
|
+
scaleBar,
|
|
2194
|
+
fontSize: labelFontSize
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
|
|
1929
2198
|
return svg.node();
|
|
1930
2199
|
} else {
|
|
1931
2200
|
throw new Error(
|
|
@@ -1937,6 +2206,7 @@ function drawPhylogeny(
|
|
|
1937
2206
|
exports.describeArc = describeArc;
|
|
1938
2207
|
exports.describeArcSweep = describeArcSweep;
|
|
1939
2208
|
exports.drawPhylogeny = drawPhylogeny;
|
|
2209
|
+
exports.ladderize = ladderize;
|
|
1940
2210
|
exports.parentFisheye = parentFisheye;
|
|
1941
2211
|
exports.phisheye = phisheye;
|
|
1942
2212
|
exports.polarToCartesian = polarToCartesian;
|
|
@@ -1944,6 +2214,8 @@ exports.radialLayout = radialLayout;
|
|
|
1944
2214
|
exports.randomTree = randomTree;
|
|
1945
2215
|
exports.readTree = readTree;
|
|
1946
2216
|
exports.rectangleLayout = rectangleLayout;
|
|
2217
|
+
exports.rotate = rotate;
|
|
1947
2218
|
exports.subTree = subTree;
|
|
2219
|
+
exports.toNewick = toNewick;
|
|
1948
2220
|
exports.unrooted = unrooted;
|
|
1949
2221
|
//# sourceMappingURL=index.cjs.map
|