@euphrasiologist/lwphylo 1.3.1 → 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 +259 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +258 -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().
|
|
@@ -1173,6 +1204,46 @@ function serialize(node) {
|
|
|
1173
1204
|
return `(${children})${label}${branch}`;
|
|
1174
1205
|
}
|
|
1175
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
|
+
|
|
1176
1247
|
function drawPhylogeny(
|
|
1177
1248
|
treeText,
|
|
1178
1249
|
{
|
|
@@ -1185,6 +1256,13 @@ function drawPhylogeny(
|
|
|
1185
1256
|
radialMode = "outer", // "outer" (co-circular tips) or "phylo" (true terminals)
|
|
1186
1257
|
tipLabels = true,
|
|
1187
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
|
|
1188
1266
|
showTooltips = true,
|
|
1189
1267
|
tooltipFormatter = (d, rtt) =>
|
|
1190
1268
|
`${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
|
|
@@ -1198,6 +1276,7 @@ function drawPhylogeny(
|
|
|
1198
1276
|
|
|
1199
1277
|
// shared helpers
|
|
1200
1278
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
1279
|
+
const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
|
|
1201
1280
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
1202
1281
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
1203
1282
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1236,6 +1315,7 @@ function drawPhylogeny(
|
|
|
1236
1315
|
const tipById = new Map(tips.map((d) => [d.thisId, d]));
|
|
1237
1316
|
const tipByLabel = new Map(tips.map((d) => [d.thisLabel, d]));
|
|
1238
1317
|
const rootToTip = makeRootToTipGetter(byId, { prefer: "x1" });
|
|
1318
|
+
const R_TIP = tipRadius ?? 2;
|
|
1239
1319
|
|
|
1240
1320
|
const maxY = d3__namespace.max(horizontal, (d) => d.y1);
|
|
1241
1321
|
const minY = d3__namespace.min(horizontal, (d) => d.y1);
|
|
@@ -1293,7 +1373,7 @@ function drawPhylogeny(
|
|
|
1293
1373
|
.join("circle")
|
|
1294
1374
|
.attr("cx", (d) => xScale(d.x1))
|
|
1295
1375
|
.attr("cy", (d) => yScale(d.y1))
|
|
1296
|
-
.attr("r",
|
|
1376
|
+
.attr("r", R_TIP)
|
|
1297
1377
|
.attr("fill", "black");
|
|
1298
1378
|
|
|
1299
1379
|
// tooltips for rect dots
|
|
@@ -1308,13 +1388,72 @@ function drawPhylogeny(
|
|
|
1308
1388
|
.on("mouseenter", function(_event, d) {
|
|
1309
1389
|
hoverLayer.selectAll("*").remove();
|
|
1310
1390
|
drawRectPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1311
|
-
d3__namespace.select(this).attr("r",
|
|
1391
|
+
d3__namespace.select(this).attr("r", R_TIP + 2);
|
|
1312
1392
|
})
|
|
1313
1393
|
.on("mouseleave", function() {
|
|
1314
1394
|
hoverLayer.selectAll("*").remove();
|
|
1315
|
-
d3__namespace.select(this).attr("r",
|
|
1395
|
+
d3__namespace.select(this).attr("r", R_TIP);
|
|
1316
1396
|
});
|
|
1317
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
|
+
|
|
1318
1457
|
// labels
|
|
1319
1458
|
if (tipLabels) {
|
|
1320
1459
|
const labels = svg
|
|
@@ -1323,7 +1462,7 @@ function drawPhylogeny(
|
|
|
1323
1462
|
.selectAll("text")
|
|
1324
1463
|
.data(tips)
|
|
1325
1464
|
.join("text")
|
|
1326
|
-
.attr("x", (d) => xScale(d.x1) + 4)
|
|
1465
|
+
.attr("x", (d) => (alignTipLabels ? alignX : xScale(d.x1)) + 4)
|
|
1327
1466
|
.attr("y", (d) => yScale(d.y1))
|
|
1328
1467
|
.attr("dy", "0.32em")
|
|
1329
1468
|
.attr("font-size", labelFontSize)
|
|
@@ -1395,6 +1534,17 @@ function drawPhylogeny(
|
|
|
1395
1534
|
}
|
|
1396
1535
|
}
|
|
1397
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
|
+
|
|
1398
1548
|
return svg.node();
|
|
1399
1549
|
} else if (layout === "radial") {
|
|
1400
1550
|
// RADIAL LAYOUT
|
|
@@ -1416,7 +1566,7 @@ function drawPhylogeny(
|
|
|
1416
1566
|
|
|
1417
1567
|
|
|
1418
1568
|
// visuals (0 = let spokes reach the dots)
|
|
1419
|
-
const DOT_R = 3;
|
|
1569
|
+
const DOT_R = tipRadius ?? 3;
|
|
1420
1570
|
const END_CAP = 0;
|
|
1421
1571
|
|
|
1422
1572
|
// ===== SCALES / BOUNDS =====
|
|
@@ -1554,6 +1704,25 @@ function drawPhylogeny(
|
|
|
1554
1704
|
.attr("stroke-width", strokeWidth);
|
|
1555
1705
|
});
|
|
1556
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
|
+
|
|
1557
1726
|
// ===== TIP DOTS =====
|
|
1558
1727
|
const tipDots = group
|
|
1559
1728
|
.append("g")
|
|
@@ -1581,6 +1750,45 @@ function drawPhylogeny(
|
|
|
1581
1750
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1582
1751
|
}
|
|
1583
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
|
+
|
|
1584
1792
|
// maps for fast lookup on hover (childId → spoke / arc)
|
|
1585
1793
|
const key = (x) => (typeof x === "string" ? +x : x);
|
|
1586
1794
|
const spokeByChild = new Map(rad.radii.map(s => [key(s.childId ?? s.thisId ?? s.id1), s]));
|
|
@@ -1601,7 +1809,8 @@ function drawPhylogeny(
|
|
|
1601
1809
|
// same tip position rule as dots/spokes:
|
|
1602
1810
|
// - "outer": snap to common ring (tipMaxR)
|
|
1603
1811
|
// - otherwise (e.g. "align"/"phylo"): true tip radius
|
|
1604
|
-
|
|
1812
|
+
// - alignTipLabels forces the common ring regardless of mode
|
|
1813
|
+
const r = (isOuter || alignTipLabels) ? tipMaxR : d.r;
|
|
1605
1814
|
const x = r * Math.cos(d.angle);
|
|
1606
1815
|
const y = r * Math.sin(d.angle);
|
|
1607
1816
|
return `translate(${xScaleRadial(x)},${yScaleRadial(y)})`;
|
|
@@ -1748,6 +1957,17 @@ function drawPhylogeny(
|
|
|
1748
1957
|
});
|
|
1749
1958
|
}
|
|
1750
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
|
+
|
|
1751
1971
|
return svg.node();
|
|
1752
1972
|
} else if (layout === "unrooted") {
|
|
1753
1973
|
// UNROOTED LAYOUT
|
|
@@ -1797,6 +2017,8 @@ function drawPhylogeny(
|
|
|
1797
2017
|
.attr("stroke-width", strokeWidth)
|
|
1798
2018
|
.attr("stroke", "#777");
|
|
1799
2019
|
|
|
2020
|
+
const R_TIP = tipRadius ?? 4;
|
|
2021
|
+
|
|
1800
2022
|
const nodes = group
|
|
1801
2023
|
.append("g")
|
|
1802
2024
|
.attr("class", "phylo_points")
|
|
@@ -1804,13 +2026,28 @@ function drawPhylogeny(
|
|
|
1804
2026
|
.data(unrootedPhylo.data)
|
|
1805
2027
|
.join("circle")
|
|
1806
2028
|
.attr("class", "dot")
|
|
1807
|
-
.attr("r", (d) => (d.isTip ?
|
|
2029
|
+
.attr("r", (d) => (d.isTip ? R_TIP : (internalNodeCircles ? internalNodeRadius : 0)))
|
|
1808
2030
|
.attr("cx", (d) => xScaleUnroot(d.x))
|
|
1809
2031
|
.attr("cy", (d) => yScaleUnroot(d.y))
|
|
1810
2032
|
.attr("stroke", "black")
|
|
1811
2033
|
.attr("stroke-width", 2)
|
|
1812
2034
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
1813
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
|
+
|
|
1814
2051
|
const byId = new Map(unrootedPhylo.data.map((d) => [d.thisId, d]));
|
|
1815
2052
|
const tipById = new Map(
|
|
1816
2053
|
unrootedPhylo.data.filter((d) => d.isTip).map((d) => [d.thisId, d])
|
|
@@ -1900,11 +2137,11 @@ function drawPhylogeny(
|
|
|
1900
2137
|
.filter((d) => d.isTip)
|
|
1901
2138
|
.on("mouseenter", function(_event, d) {
|
|
1902
2139
|
drawUnrootedPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1903
|
-
d3__namespace.select(this).attr("r",
|
|
2140
|
+
d3__namespace.select(this).attr("r", R_TIP + 2);
|
|
1904
2141
|
})
|
|
1905
2142
|
.on("mouseleave", function() {
|
|
1906
2143
|
hoverLayer.selectAll("*").remove();
|
|
1907
|
-
d3__namespace.select(this).attr("r",
|
|
2144
|
+
d3__namespace.select(this).attr("r", R_TIP);
|
|
1908
2145
|
});
|
|
1909
2146
|
|
|
1910
2147
|
if (highlightTips && highlightTips.length) {
|
|
@@ -1947,6 +2184,17 @@ function drawPhylogeny(
|
|
|
1947
2184
|
}
|
|
1948
2185
|
}
|
|
1949
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
|
+
|
|
1950
2198
|
return svg.node();
|
|
1951
2199
|
} else {
|
|
1952
2200
|
throw new Error(
|
|
@@ -1958,6 +2206,7 @@ function drawPhylogeny(
|
|
|
1958
2206
|
exports.describeArc = describeArc;
|
|
1959
2207
|
exports.describeArcSweep = describeArcSweep;
|
|
1960
2208
|
exports.drawPhylogeny = drawPhylogeny;
|
|
2209
|
+
exports.ladderize = ladderize;
|
|
1961
2210
|
exports.parentFisheye = parentFisheye;
|
|
1962
2211
|
exports.phisheye = phisheye;
|
|
1963
2212
|
exports.polarToCartesian = polarToCartesian;
|
|
@@ -1965,6 +2214,7 @@ exports.radialLayout = radialLayout;
|
|
|
1965
2214
|
exports.randomTree = randomTree;
|
|
1966
2215
|
exports.readTree = readTree;
|
|
1967
2216
|
exports.rectangleLayout = rectangleLayout;
|
|
2217
|
+
exports.rotate = rotate;
|
|
1968
2218
|
exports.subTree = subTree;
|
|
1969
2219
|
exports.toNewick = toNewick;
|
|
1970
2220
|
exports.unrooted = unrooted;
|