@euphrasiologist/lwphylo 1.2.26 → 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 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).
@@ -1862,6 +1941,7 @@ exports.parentFisheye = parentFisheye;
1862
1941
  exports.phisheye = phisheye;
1863
1942
  exports.polarToCartesian = polarToCartesian;
1864
1943
  exports.radialLayout = radialLayout;
1944
+ exports.randomTree = randomTree;
1865
1945
  exports.readTree = readTree;
1866
1946
  exports.rectangleLayout = rectangleLayout;
1867
1947
  exports.subTree = subTree;