@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.js
CHANGED
|
@@ -929,6 +929,23 @@ function unrooted (node) {
|
|
|
929
929
|
return data;
|
|
930
930
|
}
|
|
931
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Reorder every node's children by descendant tip count, so the tree
|
|
934
|
+
* "ladders" from smallest to largest clade at each branching point
|
|
935
|
+
* (or the reverse, with ascending: false). Mutates the tree in place.
|
|
936
|
+
*/
|
|
937
|
+
function ladderize(tree, { ascending = true } = {}) {
|
|
938
|
+
const sign = ascending ? 1 : -1;
|
|
939
|
+
|
|
940
|
+
(function sort(node) {
|
|
941
|
+
if (node.children.length === 0) return;
|
|
942
|
+
node.children.forEach(sort);
|
|
943
|
+
node.children.sort((a, b) => sign * (numTips(a) - numTips(b)));
|
|
944
|
+
})(tree);
|
|
945
|
+
|
|
946
|
+
return tree;
|
|
947
|
+
}
|
|
948
|
+
|
|
932
949
|
function makeIndexById(rows, key = "thisId") {
|
|
933
950
|
return new Map(rows.map(d => [d[key], d]));
|
|
934
951
|
}
|
|
@@ -938,6 +955,20 @@ function parentFisheye(d, data) {
|
|
|
938
955
|
return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
|
|
939
956
|
}
|
|
940
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Reverse the child order at one node, flipping which side of the tree
|
|
960
|
+
* that clade's descendants are drawn on. Defaults to the root. Mutates
|
|
961
|
+
* the tree in place.
|
|
962
|
+
*/
|
|
963
|
+
function rotate(tree, nodeId = tree.id) {
|
|
964
|
+
const target = preorder(tree).find((n) => n.id === nodeId);
|
|
965
|
+
if (!target) {
|
|
966
|
+
throw new Error(`No node with id ${nodeId} found in tree`);
|
|
967
|
+
}
|
|
968
|
+
target.children.reverse();
|
|
969
|
+
return tree;
|
|
970
|
+
}
|
|
971
|
+
|
|
941
972
|
/**
|
|
942
973
|
* Generate a random bifurcating tree with `nTips` tips, in the same
|
|
943
974
|
* parent/children node shape produced by readTree().
|
|
@@ -1152,6 +1183,46 @@ function serialize(node) {
|
|
|
1152
1183
|
return `(${children})${label}${branch}`;
|
|
1153
1184
|
}
|
|
1154
1185
|
|
|
1186
|
+
// Round a raw data-space length to a "nice" 1/2/5-of-a-power-of-ten value,
|
|
1187
|
+
// so an auto-sized scale bar doesn't show an ugly number like "0.347".
|
|
1188
|
+
function niceScaleLength(target) {
|
|
1189
|
+
if (!(target > 0)) return 1;
|
|
1190
|
+
const exp = Math.floor(Math.log10(target));
|
|
1191
|
+
const base = Math.pow(10, exp);
|
|
1192
|
+
const residual = target / base;
|
|
1193
|
+
const niceResidual = residual < 1.5 ? 1 : residual < 3.5 ? 2 : residual < 7.5 ? 5 : 10;
|
|
1194
|
+
return niceResidual * base;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// scaleBar: true | number (explicit data-units length) | { length, x, y, label }
|
|
1198
|
+
function addScaleBar(svg, { scale, basis, defaultX, defaultY, scaleBar, fontSize }) {
|
|
1199
|
+
const opts = (scaleBar === true || typeof scaleBar === "number") ? {} : scaleBar;
|
|
1200
|
+
const length = typeof scaleBar === "number" ? scaleBar : (opts.length ?? niceScaleLength(basis / 5));
|
|
1201
|
+
const x = opts.x ?? defaultX;
|
|
1202
|
+
const y = opts.y ?? defaultY;
|
|
1203
|
+
const barPx = scale(length) - scale(0);
|
|
1204
|
+
|
|
1205
|
+
const g = svg.append("g").attr("class", "phylo_scale_bar");
|
|
1206
|
+
g.append("line")
|
|
1207
|
+
.attr("x1", x).attr("x2", x + barPx)
|
|
1208
|
+
.attr("y1", y).attr("y2", y)
|
|
1209
|
+
.attr("stroke", "#000")
|
|
1210
|
+
.attr("stroke-width", 1);
|
|
1211
|
+
[x, x + barPx].forEach((tx) => {
|
|
1212
|
+
g.append("line")
|
|
1213
|
+
.attr("x1", tx).attr("x2", tx)
|
|
1214
|
+
.attr("y1", y - 4).attr("y2", y + 4)
|
|
1215
|
+
.attr("stroke", "#000")
|
|
1216
|
+
.attr("stroke-width", 1);
|
|
1217
|
+
});
|
|
1218
|
+
g.append("text")
|
|
1219
|
+
.attr("x", x + barPx / 2)
|
|
1220
|
+
.attr("y", y - 6)
|
|
1221
|
+
.attr("text-anchor", "middle")
|
|
1222
|
+
.attr("font-size", fontSize)
|
|
1223
|
+
.text(opts.label ?? String(length));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1155
1226
|
function drawPhylogeny(
|
|
1156
1227
|
treeText,
|
|
1157
1228
|
{
|
|
@@ -1164,6 +1235,13 @@ function drawPhylogeny(
|
|
|
1164
1235
|
radialMode = "outer", // "outer" (co-circular tips) or "phylo" (true terminals)
|
|
1165
1236
|
tipLabels = true,
|
|
1166
1237
|
labelFontSize = 10, // font size (px) for tip labels
|
|
1238
|
+
tipRadius, // px radius of tip dots; defaults to each layout's original size
|
|
1239
|
+
internalNodeCircles = false, // draw a circle at every internal (non-tip) node
|
|
1240
|
+
internalNodeRadius = 3, // px radius for internal node circles
|
|
1241
|
+
nodeLabels = false, // draw text labels at internal nodes (e.g. clade/support labels)
|
|
1242
|
+
nodeLabelFontSize, // defaults to labelFontSize
|
|
1243
|
+
scaleBar = false, // false | true | number (branch-length units) | { length, x, y, label }
|
|
1244
|
+
alignTipLabels = false, // rect & radial only: align tip labels to a common column/ring, with dashed guide lines back to the true tip position
|
|
1167
1245
|
showTooltips = true,
|
|
1168
1246
|
tooltipFormatter = (d, rtt) =>
|
|
1169
1247
|
`${d.thisLabel ?? "(unnamed)"}\nroot→tip: ${(+rtt).toFixed(4)}`,
|
|
@@ -1177,6 +1255,7 @@ function drawPhylogeny(
|
|
|
1177
1255
|
|
|
1178
1256
|
// shared helpers
|
|
1179
1257
|
const isNumber = (x) => typeof x === "number" && Number.isFinite(x);
|
|
1258
|
+
const nodeLabelSize = nodeLabelFontSize ?? labelFontSize;
|
|
1180
1259
|
// Works for both radial (uses `r`) and rect (uses `x1`).
|
|
1181
1260
|
// Falls back to summing branchLength up to the root if neither is present.
|
|
1182
1261
|
function makeRootToTipGetter(byId, { prefer = "auto" } = {}) {
|
|
@@ -1215,6 +1294,7 @@ function drawPhylogeny(
|
|
|
1215
1294
|
const tipById = new Map(tips.map((d) => [d.thisId, d]));
|
|
1216
1295
|
const tipByLabel = new Map(tips.map((d) => [d.thisLabel, d]));
|
|
1217
1296
|
const rootToTip = makeRootToTipGetter(byId, { prefer: "x1" });
|
|
1297
|
+
const R_TIP = tipRadius ?? 2;
|
|
1218
1298
|
|
|
1219
1299
|
const maxY = d3.max(horizontal, (d) => d.y1);
|
|
1220
1300
|
const minY = d3.min(horizontal, (d) => d.y1);
|
|
@@ -1272,7 +1352,7 @@ function drawPhylogeny(
|
|
|
1272
1352
|
.join("circle")
|
|
1273
1353
|
.attr("cx", (d) => xScale(d.x1))
|
|
1274
1354
|
.attr("cy", (d) => yScale(d.y1))
|
|
1275
|
-
.attr("r",
|
|
1355
|
+
.attr("r", R_TIP)
|
|
1276
1356
|
.attr("fill", "black");
|
|
1277
1357
|
|
|
1278
1358
|
// tooltips for rect dots
|
|
@@ -1287,13 +1367,72 @@ function drawPhylogeny(
|
|
|
1287
1367
|
.on("mouseenter", function(_event, d) {
|
|
1288
1368
|
hoverLayer.selectAll("*").remove();
|
|
1289
1369
|
drawRectPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1290
|
-
d3.select(this).attr("r",
|
|
1370
|
+
d3.select(this).attr("r", R_TIP + 2);
|
|
1291
1371
|
})
|
|
1292
1372
|
.on("mouseleave", function() {
|
|
1293
1373
|
hoverLayer.selectAll("*").remove();
|
|
1294
|
-
d3.select(this).attr("r",
|
|
1374
|
+
d3.select(this).attr("r", R_TIP);
|
|
1295
1375
|
});
|
|
1296
1376
|
|
|
1377
|
+
// internal node circles (optional)
|
|
1378
|
+
if (internalNodeCircles) {
|
|
1379
|
+
const internalNodes = tree_df.data.filter((d) => !d.isTip);
|
|
1380
|
+
const internalDots = group
|
|
1381
|
+
.append("g")
|
|
1382
|
+
.attr("class", "phylo_internal_dots")
|
|
1383
|
+
.selectAll("circle")
|
|
1384
|
+
.data(internalNodes)
|
|
1385
|
+
.join("circle")
|
|
1386
|
+
.attr("cx", (d) => xScale(d.x1))
|
|
1387
|
+
.attr("cy", (d) => yScale(d.y1))
|
|
1388
|
+
.attr("r", internalNodeRadius)
|
|
1389
|
+
.attr("fill", "white")
|
|
1390
|
+
.attr("stroke", "#555")
|
|
1391
|
+
.attr("stroke-width", 1);
|
|
1392
|
+
|
|
1393
|
+
if (showTooltips) {
|
|
1394
|
+
internalDots
|
|
1395
|
+
.append("title")
|
|
1396
|
+
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// internal node labels (optional)
|
|
1401
|
+
if (nodeLabels) {
|
|
1402
|
+
const labeledInternalNodes = tree_df.data.filter((d) => !d.isTip && d.thisLabel);
|
|
1403
|
+
svg
|
|
1404
|
+
.append("g")
|
|
1405
|
+
.attr("class", "phylo_node_labels")
|
|
1406
|
+
.selectAll("text")
|
|
1407
|
+
.data(labeledInternalNodes)
|
|
1408
|
+
.join("text")
|
|
1409
|
+
.attr("x", (d) => xScale(d.x1) - 4)
|
|
1410
|
+
.attr("y", (d) => yScale(d.y1) - 4)
|
|
1411
|
+
.attr("text-anchor", "end")
|
|
1412
|
+
.attr("font-size", nodeLabelSize)
|
|
1413
|
+
.text((d) => d.thisLabel);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// column that tip labels align to when alignTipLabels is set
|
|
1417
|
+
const alignX = xScale(maxX);
|
|
1418
|
+
|
|
1419
|
+
// dashed guide lines from each tip's true branch end to the aligned label column
|
|
1420
|
+
if (tipLabels && alignTipLabels) {
|
|
1421
|
+
group
|
|
1422
|
+
.append("g")
|
|
1423
|
+
.attr("class", "phylo_align_guides")
|
|
1424
|
+
.selectAll("line")
|
|
1425
|
+
.data(tips)
|
|
1426
|
+
.join("line")
|
|
1427
|
+
.attr("x1", (d) => xScale(d.x1))
|
|
1428
|
+
.attr("x2", alignX)
|
|
1429
|
+
.attr("y1", (d) => yScale(d.y1))
|
|
1430
|
+
.attr("y2", (d) => yScale(d.y1))
|
|
1431
|
+
.attr("stroke", "#999")
|
|
1432
|
+
.attr("stroke-width", 1)
|
|
1433
|
+
.attr("stroke-dasharray", "2,2");
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1297
1436
|
// labels
|
|
1298
1437
|
if (tipLabels) {
|
|
1299
1438
|
const labels = svg
|
|
@@ -1302,7 +1441,7 @@ function drawPhylogeny(
|
|
|
1302
1441
|
.selectAll("text")
|
|
1303
1442
|
.data(tips)
|
|
1304
1443
|
.join("text")
|
|
1305
|
-
.attr("x", (d) => xScale(d.x1) + 4)
|
|
1444
|
+
.attr("x", (d) => (alignTipLabels ? alignX : xScale(d.x1)) + 4)
|
|
1306
1445
|
.attr("y", (d) => yScale(d.y1))
|
|
1307
1446
|
.attr("dy", "0.32em")
|
|
1308
1447
|
.attr("font-size", labelFontSize)
|
|
@@ -1374,6 +1513,17 @@ function drawPhylogeny(
|
|
|
1374
1513
|
}
|
|
1375
1514
|
}
|
|
1376
1515
|
|
|
1516
|
+
if (scaleBar) {
|
|
1517
|
+
addScaleBar(svg, {
|
|
1518
|
+
scale: xScale,
|
|
1519
|
+
basis: maxX,
|
|
1520
|
+
defaultX: margin.left,
|
|
1521
|
+
defaultY: height - margin.bottom / 2,
|
|
1522
|
+
scaleBar,
|
|
1523
|
+
fontSize: labelFontSize
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1377
1527
|
return svg.node();
|
|
1378
1528
|
} else if (layout === "radial") {
|
|
1379
1529
|
// RADIAL LAYOUT
|
|
@@ -1395,7 +1545,7 @@ function drawPhylogeny(
|
|
|
1395
1545
|
|
|
1396
1546
|
|
|
1397
1547
|
// visuals (0 = let spokes reach the dots)
|
|
1398
|
-
const DOT_R = 3;
|
|
1548
|
+
const DOT_R = tipRadius ?? 3;
|
|
1399
1549
|
const END_CAP = 0;
|
|
1400
1550
|
|
|
1401
1551
|
// ===== SCALES / BOUNDS =====
|
|
@@ -1533,6 +1683,25 @@ function drawPhylogeny(
|
|
|
1533
1683
|
.attr("stroke-width", strokeWidth);
|
|
1534
1684
|
});
|
|
1535
1685
|
|
|
1686
|
+
// ===== ALIGN GUIDES (optional) =====
|
|
1687
|
+
// dashed lines from each tip's true position to the common label ring,
|
|
1688
|
+
// for radialMode "phylo" (true terminals) where tips aren't already co-circular
|
|
1689
|
+
if (tipLabels && alignTipLabels && !isOuter) {
|
|
1690
|
+
group
|
|
1691
|
+
.append("g")
|
|
1692
|
+
.attr("class", "phylo_align_guides")
|
|
1693
|
+
.selectAll("line")
|
|
1694
|
+
.data(tips)
|
|
1695
|
+
.join("line")
|
|
1696
|
+
.attr("x1", (d) => xScaleRadial(d.x))
|
|
1697
|
+
.attr("y1", (d) => yScaleRadial(d.y))
|
|
1698
|
+
.attr("x2", (d) => xScaleRadial(tipMaxR * Math.cos(d.angle)))
|
|
1699
|
+
.attr("y2", (d) => yScaleRadial(tipMaxR * Math.sin(d.angle)))
|
|
1700
|
+
.attr("stroke", "#999")
|
|
1701
|
+
.attr("stroke-width", 1)
|
|
1702
|
+
.attr("stroke-dasharray", "2,2");
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1536
1705
|
// ===== TIP DOTS =====
|
|
1537
1706
|
const tipDots = group
|
|
1538
1707
|
.append("g")
|
|
@@ -1560,6 +1729,45 @@ function drawPhylogeny(
|
|
|
1560
1729
|
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1561
1730
|
}
|
|
1562
1731
|
|
|
1732
|
+
// ===== INTERNAL NODE CIRCLES (optional) =====
|
|
1733
|
+
if (internalNodeCircles) {
|
|
1734
|
+
const internalNodes = rad.data.filter((d) => !d.isTip);
|
|
1735
|
+
const internalDots = group
|
|
1736
|
+
.append("g")
|
|
1737
|
+
.attr("class", "phylo_internal_dots")
|
|
1738
|
+
.selectAll("circle")
|
|
1739
|
+
.data(internalNodes)
|
|
1740
|
+
.join("circle")
|
|
1741
|
+
.attr("cx", (d) => xScaleRadial(d.x))
|
|
1742
|
+
.attr("cy", (d) => yScaleRadial(d.y))
|
|
1743
|
+
.attr("r", internalNodeRadius)
|
|
1744
|
+
.attr("fill", "white")
|
|
1745
|
+
.attr("stroke", "#555")
|
|
1746
|
+
.attr("stroke-width", 1);
|
|
1747
|
+
|
|
1748
|
+
if (showTooltips) {
|
|
1749
|
+
internalDots
|
|
1750
|
+
.append("title")
|
|
1751
|
+
.text((d) => tooltipFormatter(d, rootToTip(d.thisId)));
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// ===== INTERNAL NODE LABELS (optional) =====
|
|
1756
|
+
if (nodeLabels) {
|
|
1757
|
+
const labeledInternalNodes = rad.data.filter((d) => !d.isTip && d.thisLabel);
|
|
1758
|
+
group
|
|
1759
|
+
.append("g")
|
|
1760
|
+
.attr("class", "phylo_node_labels")
|
|
1761
|
+
.selectAll("text")
|
|
1762
|
+
.data(labeledInternalNodes)
|
|
1763
|
+
.join("text")
|
|
1764
|
+
.attr("x", (d) => xScaleRadial(d.x) + 4)
|
|
1765
|
+
.attr("y", (d) => yScaleRadial(d.y) - 4)
|
|
1766
|
+
.attr("font-size", nodeLabelSize)
|
|
1767
|
+
.attr("fill", "black")
|
|
1768
|
+
.text((d) => d.thisLabel);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1563
1771
|
// maps for fast lookup on hover (childId → spoke / arc)
|
|
1564
1772
|
const key = (x) => (typeof x === "string" ? +x : x);
|
|
1565
1773
|
const spokeByChild = new Map(rad.radii.map(s => [key(s.childId ?? s.thisId ?? s.id1), s]));
|
|
@@ -1580,7 +1788,8 @@ function drawPhylogeny(
|
|
|
1580
1788
|
// same tip position rule as dots/spokes:
|
|
1581
1789
|
// - "outer": snap to common ring (tipMaxR)
|
|
1582
1790
|
// - otherwise (e.g. "align"/"phylo"): true tip radius
|
|
1583
|
-
|
|
1791
|
+
// - alignTipLabels forces the common ring regardless of mode
|
|
1792
|
+
const r = (isOuter || alignTipLabels) ? tipMaxR : d.r;
|
|
1584
1793
|
const x = r * Math.cos(d.angle);
|
|
1585
1794
|
const y = r * Math.sin(d.angle);
|
|
1586
1795
|
return `translate(${xScaleRadial(x)},${yScaleRadial(y)})`;
|
|
@@ -1727,6 +1936,17 @@ function drawPhylogeny(
|
|
|
1727
1936
|
});
|
|
1728
1937
|
}
|
|
1729
1938
|
|
|
1939
|
+
if (scaleBar) {
|
|
1940
|
+
addScaleBar(svg, {
|
|
1941
|
+
scale: xScaleRadial,
|
|
1942
|
+
basis: maxRadius,
|
|
1943
|
+
defaultX: 20,
|
|
1944
|
+
defaultY: h - 20,
|
|
1945
|
+
scaleBar,
|
|
1946
|
+
fontSize: labelFontSize
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1730
1950
|
return svg.node();
|
|
1731
1951
|
} else if (layout === "unrooted") {
|
|
1732
1952
|
// UNROOTED LAYOUT
|
|
@@ -1776,6 +1996,8 @@ function drawPhylogeny(
|
|
|
1776
1996
|
.attr("stroke-width", strokeWidth)
|
|
1777
1997
|
.attr("stroke", "#777");
|
|
1778
1998
|
|
|
1999
|
+
const R_TIP = tipRadius ?? 4;
|
|
2000
|
+
|
|
1779
2001
|
const nodes = group
|
|
1780
2002
|
.append("g")
|
|
1781
2003
|
.attr("class", "phylo_points")
|
|
@@ -1783,13 +2005,28 @@ function drawPhylogeny(
|
|
|
1783
2005
|
.data(unrootedPhylo.data)
|
|
1784
2006
|
.join("circle")
|
|
1785
2007
|
.attr("class", "dot")
|
|
1786
|
-
.attr("r", (d) => (d.isTip ?
|
|
2008
|
+
.attr("r", (d) => (d.isTip ? R_TIP : (internalNodeCircles ? internalNodeRadius : 0)))
|
|
1787
2009
|
.attr("cx", (d) => xScaleUnroot(d.x))
|
|
1788
2010
|
.attr("cy", (d) => yScaleUnroot(d.y))
|
|
1789
2011
|
.attr("stroke", "black")
|
|
1790
2012
|
.attr("stroke-width", 2)
|
|
1791
2013
|
.attr("fill", (d) => (d.isTip ? "black" : "white"));
|
|
1792
2014
|
|
|
2015
|
+
if (nodeLabels) {
|
|
2016
|
+
const labeledInternalNodes = unrootedPhylo.data.filter((d) => !d.isTip && d.thisLabel);
|
|
2017
|
+
group
|
|
2018
|
+
.append("g")
|
|
2019
|
+
.attr("class", "phylo_node_labels")
|
|
2020
|
+
.selectAll("text")
|
|
2021
|
+
.data(labeledInternalNodes)
|
|
2022
|
+
.join("text")
|
|
2023
|
+
.attr("x", (d) => xScaleUnroot(d.x) + 4)
|
|
2024
|
+
.attr("y", (d) => yScaleUnroot(d.y) - 4)
|
|
2025
|
+
.attr("font-size", nodeLabelSize)
|
|
2026
|
+
.attr("fill", "black")
|
|
2027
|
+
.text((d) => d.thisLabel);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
1793
2030
|
const byId = new Map(unrootedPhylo.data.map((d) => [d.thisId, d]));
|
|
1794
2031
|
const tipById = new Map(
|
|
1795
2032
|
unrootedPhylo.data.filter((d) => d.isTip).map((d) => [d.thisId, d])
|
|
@@ -1879,11 +2116,11 @@ function drawPhylogeny(
|
|
|
1879
2116
|
.filter((d) => d.isTip)
|
|
1880
2117
|
.on("mouseenter", function(_event, d) {
|
|
1881
2118
|
drawUnrootedPath(d.thisId, hoverLayer, hoverStroke, hoverWidth);
|
|
1882
|
-
d3.select(this).attr("r",
|
|
2119
|
+
d3.select(this).attr("r", R_TIP + 2);
|
|
1883
2120
|
})
|
|
1884
2121
|
.on("mouseleave", function() {
|
|
1885
2122
|
hoverLayer.selectAll("*").remove();
|
|
1886
|
-
d3.select(this).attr("r",
|
|
2123
|
+
d3.select(this).attr("r", R_TIP);
|
|
1887
2124
|
});
|
|
1888
2125
|
|
|
1889
2126
|
if (highlightTips && highlightTips.length) {
|
|
@@ -1926,6 +2163,17 @@ function drawPhylogeny(
|
|
|
1926
2163
|
}
|
|
1927
2164
|
}
|
|
1928
2165
|
|
|
2166
|
+
if (scaleBar) {
|
|
2167
|
+
addScaleBar(svg, {
|
|
2168
|
+
scale: xScaleUnroot,
|
|
2169
|
+
basis: maxRadius,
|
|
2170
|
+
defaultX: 20,
|
|
2171
|
+
defaultY: h - 20,
|
|
2172
|
+
scaleBar,
|
|
2173
|
+
fontSize: labelFontSize
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
|
|
1929
2177
|
return svg.node();
|
|
1930
2178
|
} else {
|
|
1931
2179
|
throw new Error(
|
|
@@ -1934,5 +2182,5 @@ function drawPhylogeny(
|
|
|
1934
2182
|
}
|
|
1935
2183
|
}
|
|
1936
2184
|
|
|
1937
|
-
export { describeArc, describeArcSweep, drawPhylogeny, parentFisheye, phisheye, polarToCartesian, radialLayout, randomTree, readTree, rectangleLayout, subTree, toNewick, unrooted };
|
|
2185
|
+
export { describeArc, describeArcSweep, drawPhylogeny, ladderize, parentFisheye, phisheye, polarToCartesian, radialLayout, randomTree, readTree, rectangleLayout, rotate, subTree, toNewick, unrooted };
|
|
1938
2186
|
//# sourceMappingURL=index.js.map
|