@euphrasiologist/lwphylo 1.2.26 → 1.3.1

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 CHANGED
@@ -2,16 +2,15 @@
2
2
 
3
3
  A lightweight, low level javascript library to plot phylogenies from a Newick file. It uses no dependencies on any other package, but is designed to be given to the D3 library for visualisation.
4
4
 
5
- ### Functionality
5
+ ## Website
6
6
 
7
- Newick trees can be parsed using the `readTree()` function. This object can then be wrapped in three main functions; `rectangleLayout()` to produce a "regular" phylogenetic tree, `radialLayout()` to produce a circular phylogeny, and `unrooted()` to produce an unrooted tree via the equal angle layout algorithm.
7
+ Visit https://euphrasiologist.github.io/lwPhylo/ to see examples and live rendering of trees. Can even paste your own in.
8
8
 
9
- ### Examples
9
+ ### Functionality
10
10
 
11
- A quick tutorial is now available to see on Observable: https://observablehq.com/@euphrasiologist/lwphylo-tutorial \
12
- It goes over the three tree layout functions, and hopefully is all quite straightforward.
11
+ Newick trees can be parsed using the `readTree()` function. This object can then be wrapped in three main functions; `rectangleLayout()` to produce a "regular" phylogenetic tree, `radialLayout()` to produce a circular phylogeny, and `unrooted()` to produce an unrooted tree via the equal angle layout algorithm.
13
12
 
14
- Stay tuned for examples in the browser.
13
+ Need a tree to experiment with? `randomTree(nTips, { maxBranchLength, labelPrefix, seed })` generates a random bifurcating tree in the same node shape as `readTree()`, ready to pass straight into any of the layout functions.
15
14
 
16
15
  ### Acknowledgements
17
16
 
package/dist/index.cjs CHANGED
@@ -959,6 +959,85 @@ function parentFisheye(d, data) {
959
959
  return parent ? { px: parent.fisheye.x, py: parent.fisheye.y } : null;
960
960
  }
961
961
 
962
+ /**
963
+ * Generate a random bifurcating tree with `nTips` tips, in the same
964
+ * parent/children node shape produced by readTree().
965
+ *
966
+ * Topology is grown by repeatedly picking a random extant lineage to split
967
+ * (a Yule/coalescent-style process), so internal branching order is random
968
+ * rather than a fixed balanced/caterpillar shape. Branch lengths are drawn
969
+ * uniformly from [0, maxBranchLength).
970
+ */
971
+
972
+ function randomTree(nTips = 10, {
973
+ maxBranchLength = 1,
974
+ labelPrefix = 't',
975
+ seed = null
976
+ } = {}) {
977
+ if (!Number.isInteger(nTips) || nTips < 1) {
978
+ throw new Error("nTips must be a positive integer");
979
+ }
980
+
981
+ const random = seed == null ? Math.random : mulberry32(seed);
982
+
983
+ let nodeId = 0;
984
+ const makeNode = (parent) => ({
985
+ parent,
986
+ children: [],
987
+ id: nodeId++,
988
+ label: '',
989
+ branchLength: null
990
+ });
991
+
992
+ const root = makeNode(null);
993
+
994
+ if (nTips === 1) {
995
+ root.label = `${labelPrefix}1`;
996
+ return root;
997
+ }
998
+
999
+ // start with two lineages hanging off the root
1000
+ let lineages = [makeNode(root), makeNode(root)];
1001
+ root.children.push(...lineages);
1002
+
1003
+ // repeatedly split a random lineage until we have nTips of them
1004
+ while (lineages.length < nTips) {
1005
+ const i = Math.floor(random() * lineages.length);
1006
+ const parent = lineages[i];
1007
+ const left = makeNode(parent);
1008
+ const right = makeNode(parent);
1009
+ parent.children.push(left, right);
1010
+ lineages.splice(i, 1, left, right);
1011
+ }
1012
+
1013
+ // assign branch lengths to every non-root node, and tip labels in
1014
+ // left-to-right order
1015
+ let tipIndex = 0;
1016
+ const assign = (node) => {
1017
+ for (const child of node.children) {
1018
+ child.branchLength = random() * maxBranchLength;
1019
+ assign(child);
1020
+ }
1021
+ if (node.children.length === 0) {
1022
+ node.label = `${labelPrefix}${++tipIndex}`;
1023
+ }
1024
+ };
1025
+ assign(root);
1026
+
1027
+ return root;
1028
+ }
1029
+
1030
+ // small deterministic PRNG so `seed` gives reproducible trees
1031
+ function mulberry32(seed) {
1032
+ let a = seed >>> 0;
1033
+ return function () {
1034
+ a |= 0; a = (a + 0x6D2B79F5) | 0;
1035
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
1036
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
1037
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
1038
+ };
1039
+ }
1040
+
962
1041
  /**
963
1042
  * Parse a Newick tree string into a doubly-linked list of JS Objects.
964
1043
  * Assigns labels, branch lengths, and node IDs (tips before internals if input emits them that way).
@@ -1073,6 +1152,27 @@ function subTree (tree, node) {
1073
1152
  return Object.fromEntries(data.concat(filtered));
1074
1153
  }
1075
1154
 
1155
+ /**
1156
+ * Serialize a parsed tree (the parent/children node shape produced by
1157
+ * readTree() and randomTree()) back into a Newick string.
1158
+ */
1159
+
1160
+ function toNewick(node) {
1161
+ return `${serialize(node)};`;
1162
+ }
1163
+
1164
+ function serialize(node) {
1165
+ const label = node.label || '';
1166
+ const branch = node.branchLength == null ? '' : `:${node.branchLength}`;
1167
+
1168
+ if (node.children.length === 0) {
1169
+ return `${label}${branch}`;
1170
+ }
1171
+
1172
+ const children = node.children.map(serialize).join(',');
1173
+ return `(${children})${label}${branch}`;
1174
+ }
1175
+
1076
1176
  function drawPhylogeny(
1077
1177
  treeText,
1078
1178
  {
@@ -1862,8 +1962,10 @@ exports.parentFisheye = parentFisheye;
1862
1962
  exports.phisheye = phisheye;
1863
1963
  exports.polarToCartesian = polarToCartesian;
1864
1964
  exports.radialLayout = radialLayout;
1965
+ exports.randomTree = randomTree;
1865
1966
  exports.readTree = readTree;
1866
1967
  exports.rectangleLayout = rectangleLayout;
1867
1968
  exports.subTree = subTree;
1969
+ exports.toNewick = toNewick;
1868
1970
  exports.unrooted = unrooted;
1869
1971
  //# sourceMappingURL=index.cjs.map