@euphrasiologist/lwphylo 1.1.5 → 1.1.7
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// https://github.com/Euphrasiologist/lwPhylo#readme v1.1.
|
|
1
|
+
// https://github.com/Euphrasiologist/lwPhylo#readme v1.1.7 Copyright 2025 Max Brown
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
@@ -468,124 +468,79 @@ function radialLayout (node) {
|
|
|
468
468
|
* Attempted copy of .layout.rect() function here https://github.com/ArtPoon/ggfree/blob/master/R/tree.R
|
|
469
469
|
*/
|
|
470
470
|
|
|
471
|
-
function getHorizontal
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
//
|
|
475
|
-
pd.map(d => d.
|
|
476
|
-
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (pd[i].thisId === child_2) {
|
|
495
|
-
var y2 = pd[i].y0;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return mean(([y1, y2]))
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// if the node is not a tip...
|
|
502
|
-
for (let i = 0; i < pd.length; i++) {
|
|
503
|
-
if (pd[i].isTip === false) {
|
|
504
|
-
// then y0 === y1 and is the mean of the parental nodes
|
|
505
|
-
pd[i].y0 = yVals(pd[i].children[0], pd[i].children[1]);
|
|
506
|
-
pd[i].y1 = yVals(pd[i].children[0], pd[i].children[1]);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// find root
|
|
511
|
-
var root = pd.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
|
|
512
|
-
|
|
513
|
-
// sort the data temporarily in decreasing parentId
|
|
514
|
-
pd.sort((a, b) => b.thisId - a.thisId);
|
|
515
|
-
|
|
516
|
-
// get the branchlength of the parentID
|
|
517
|
-
function getParentBranchLength(current_parentId) {
|
|
518
|
-
for (var i = 0; i < pd.length; i++) {
|
|
519
|
-
if (pd[i].thisId === current_parentId) {
|
|
520
|
-
var branchLength = pd[i].x1;
|
|
521
|
-
}
|
|
471
|
+
function getHorizontal(node) {
|
|
472
|
+
const pd = fortify(node);
|
|
473
|
+
|
|
474
|
+
// Fast lookup from id -> pd index
|
|
475
|
+
const idIndex = new Map(pd.map((d, i) => [d.thisId, i]));
|
|
476
|
+
|
|
477
|
+
// 1) Leaf order from the INPUT TREE (respects your child order / ladderize)
|
|
478
|
+
const leafIds = [];
|
|
479
|
+
(function dfs(n) {
|
|
480
|
+
if (!n.children || n.children.length === 0) { leafIds.push(n.id); return; }
|
|
481
|
+
n.children.forEach(dfs);
|
|
482
|
+
})(node);
|
|
483
|
+
|
|
484
|
+
// Map each leaf id to a vertical slot (1..N)
|
|
485
|
+
const tipSlot = new Map(leafIds.map((id, i) => [id, i + 1]));
|
|
486
|
+
|
|
487
|
+
// 2) Set Y for tips directly from that order; compute Y for internal nodes via postorder
|
|
488
|
+
(function setY(n) {
|
|
489
|
+
const i = idIndex.get(n.id);
|
|
490
|
+
if (!n.children || n.children.length === 0) {
|
|
491
|
+
const y = tipSlot.get(n.id);
|
|
492
|
+
pd[i].y0 = y; pd[i].y1 = y;
|
|
493
|
+
return y;
|
|
522
494
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// return the original sorted data
|
|
544
|
-
pd.sort((a, b) => a.thisId - b.thisId);
|
|
545
|
-
|
|
546
|
-
// remove root?
|
|
547
|
-
|
|
548
|
-
// finally get rid of unwanted y, x and angle properties
|
|
549
|
-
/* eslint-disable no-unused-vars */
|
|
495
|
+
const ys = n.children.map(setY);
|
|
496
|
+
const y = mean(ys);
|
|
497
|
+
pd[i].y0 = y; pd[i].y1 = y;
|
|
498
|
+
return y;
|
|
499
|
+
})(node);
|
|
500
|
+
|
|
501
|
+
// 3) Set X by accumulating branch lengths down the tree (no sorting-by-id needed)
|
|
502
|
+
(function setX(n, xParent) {
|
|
503
|
+
const i = idIndex.get(n.id);
|
|
504
|
+
const bl = pd[i].branchLength ?? 0;
|
|
505
|
+
const x0 = xParent ?? 0;
|
|
506
|
+
const x1 = x0 + bl;
|
|
507
|
+
pd[i].x0 = x0; pd[i].x1 = x1;
|
|
508
|
+
if (n.children && n.children.length) n.children.forEach(c => setX(c, x1));
|
|
509
|
+
})(node, 0);
|
|
510
|
+
|
|
511
|
+
// Clean up and return
|
|
550
512
|
return pd.map(({ y, x, angle, ...item }) => item);
|
|
551
|
-
/* eslint-enable no-unused-vars */
|
|
552
513
|
}
|
|
553
514
|
|
|
554
|
-
/**
|
|
555
|
-
* Get the vertical lines to draw.
|
|
556
|
-
*/
|
|
557
|
-
|
|
558
515
|
function getVertical (node) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
return height;
|
|
516
|
+
const data = getHorizontal(node);
|
|
517
|
+
|
|
518
|
+
// Group rows by parentId (children that share a parent)
|
|
519
|
+
const byParent = new Map();
|
|
520
|
+
for (const row of data) {
|
|
521
|
+
if (row.parentId == null) continue;
|
|
522
|
+
const a = byParent.get(row.parentId);
|
|
523
|
+
if (a) a.push(row); else byParent.set(row.parentId, [row]);
|
|
570
524
|
}
|
|
571
525
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
526
|
+
const verticals = [];
|
|
527
|
+
for (const [parentId, kids] of byParent.entries()) {
|
|
528
|
+
if (!kids.length) continue;
|
|
529
|
+
// Works for binary and multifurcations:
|
|
530
|
+
const yvals = kids.map(d => d.y0);
|
|
531
|
+
const y0 = Math.min(...yvals);
|
|
532
|
+
const y1 = Math.max(...yvals);
|
|
533
|
+
// All children share the same junction x (their x0)
|
|
534
|
+
const x = kids[0].x0;
|
|
535
|
+
|
|
536
|
+
verticals.push({
|
|
537
|
+
parentId,
|
|
538
|
+
x0: x,
|
|
539
|
+
x1: x,
|
|
540
|
+
y0,
|
|
541
|
+
y1,
|
|
542
|
+
heights: y1 - y0
|
|
543
|
+
});
|
|
589
544
|
}
|
|
590
545
|
|
|
591
546
|
return verticals;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// https://github.com/Euphrasiologist/lwPhylo#readme v1.1.
|
|
2
|
-
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).lwphylo=n.lwphylo||{})}(this,(function(n){"use strict";const t={circular:()=>{var n,t,
|
|
1
|
+
// https://github.com/Euphrasiologist/lwPhylo#readme v1.1.7 Copyright 2025 Max Brown
|
|
2
|
+
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).lwphylo=n.lwphylo||{})}(this,(function(n){"use strict";const t={circular:()=>{var n,t,e=200,r=2,a=[0,0],i={};function l(r){var l=i.xscale(r.x)-a[0],h=i.yscale(r.y)-a[1],s=Math.sqrt(l*l+h*h);if(!s||s>=e)return{x:i.xscale(r.x),y:i.yscale(r.y),z:s>=e?1:10};var o=n*(1-Math.exp(-s*t))/s*.75+.25;return{x:a[0]+l*o,y:a[1]+h*o,z:Math.min(o,10)}}function h(){return n=(n=Math.exp(r))/(n-1)*e,t=r/e,l}return l.radius=function(n){return arguments.length?(e=+n,h()):e},l.distortion=function(n){return arguments.length?(r=+n,h()):r},l.focus=function(n){return arguments.length?(a=n,l):a},l.scales=function(n,t){return arguments.length?(i={xscale:n,yscale:t},l):i},h()}};function e(n,t,e,r){return{x:n+e*Math.cos(r),y:t+e*Math.sin(r)}}function r(n,t=!0){var e=[];for(const t of function n(t,e=[]){e.push(t);for(var r=0;r<t.children.length;r++)e=n(t.children[r],e);return e}(n))null===t.parent?e.push({parentId:null,parentLabel:null,thisId:t.id,thisLabel:t.label,children:t.children.map(n=>n.id),branchLength:0,isTip:!1,x:t.x,y:t.y,angle:t.angle}):e.push({parentId:t.parent.id,parentLabel:t.parent.label,thisId:t.id,thisLabel:t.label,children:t.children.map(n=>n.id),branchLength:t.branchLength,isTip:0==t.children.length,x:t.x,y:t.y,angle:t.angle});return t&&(e=e.sort((function(n,t){return n.thisId-t.thisId}))),e}function a(n){var t=0;for(const e of function(n){for(var t,e=[n],r=[];e.length>0;){t=e.pop(),r.push(t);for(const n of t.children)e.push(n)}return r}(n))0==e.children.length&&t++;return t}function i(n,t){let e=0,r=0;if(void 0===t)for(let t of n)null!=t&&(t=+t)>=t&&(++e,r+=t);else{let a=-1;for(let i of n)null!=(i=t(i,++a,n))&&(i=+i)>=i&&(++e,r+=i)}if(e)return r/e}function l(n){var t=r(n),e=a(n),l=1;for(let n=0;n<t.length;n++)1==t[n].isTip&&(t[n].angle=l/e*2*Math.PI,l+=1);function h(n,e){for(var r=0;r<t.length;r++){if(t[r].thisId===n)var a=t[r].angle;if(t[r].thisId===e)var l=t[r].angle}return i([a,l])}for(let n=0;n<t.length;n++)!1===t[n].isTip&&(t[n].angle=h(t[n].children[0],t[n].children[1]));var s=t.map(n=>null===n.parentId?n.thisId:null).filter(n=>null!=n)[0];function o(n){for(var e=0;e<t.length;e++)if(t[e].thisId===n)var r=t[e].r;return r}t.sort((n,t)=>t.thisId-n.thisId);for(let n=0;n<t.length;n++)if(t[n].parentId===s)!0===t[n].isTip?(t[n].r=t[n].branchLength,t[n].x=t[n].branchLength,t[n].y=0):(t[n].r=t[n].branchLength,t[n].x=t[n].branchLength*Math.cos(t[n].angle),t[n].y=t[n].branchLength*Math.sin(t[n].angle));else{var c=o(t[n].parentId);t[n].r=c+t[n].branchLength,t[n].x=(c+t[n].branchLength)*Math.cos(t[n].angle),t[n].y=(c+t[n].branchLength)*Math.sin(t[n].angle)}return t[0].r=0,t[0].x=0,t[0].y=0,t}function h(n,t){const e=Math.cos(n),r=Math.sin(n);return(a=Math.atan2(..."X"===t?[r,-e]:[-r,e])).toString().includes("3.1415")&&(a=Math.abs(a)),a;var a}function s(n){const t=r(n),e=new Map(t.map((n,t)=>[n.thisId,t])),a=[];!function n(t){t.children&&0!==t.children.length?t.children.forEach(n):a.push(t.id)}(n);const l=new Map(a.map((n,t)=>[n,t+1]));return function n(r){const a=e.get(r.id);if(!r.children||0===r.children.length){const n=l.get(r.id);return t[a].y0=n,t[a].y1=n,n}const h=i(r.children.map(n));return t[a].y0=h,t[a].y1=h,h}(n),function n(r,a){const i=e.get(r.id),l=a??0,h=l+(t[i].branchLength??0);t[i].x0=l,t[i].x1=h,r.children&&r.children.length&&r.children.forEach(t=>n(t,h))}(n,0),t.map(({y:n,x:t,angle:e,...r})=>r)}n.describeArc=function(n,t,r,a,i){var l=e(n,t,r,a),h=e(n,t,r,i);return["M",l.x,l.y,"A",r,r,0,0,0,h.x,h.y].join(" ")},n.parentFisheye=function(n,t){for(let e=0;e<t.length;e++)if(n.parentId===t[e].thisId)return{px:t[e].fisheye.x,py:t[e].fisheye.y}},n.phisheye=t,n.radialLayout=function(n){var t={};return t.data=l(n),t.radii=function(n){var t=l(n);function e(n){for(var e=0;e<t.length;e++)if(t[e].thisId===n.parentId)var r=t[e].r;return r}for(var r=[],a=t.map(n=>null===n.parentId?n.thisId:null).filter(n=>null!=n)[0],i=0;i<t.length;i++)t[i].thisId!==a&&r.push({thisId:t[i].thisId,thisLabel:t[i].thisLabel,x0:t[i].x,x1:e(t[i])*Math.cos(t[i].angle),y0:t[i].y,y1:e(t[i])*Math.sin(t[i].angle),isTip:t[i].isTip});return r}(n),t.arcs=function(n){var t=[],e=n.map(n=>null===n.parentId?n.thisId:null).filter(n=>null!=n)[0];function r(t){for(var e=0;e<n.length;e++)if(n[e].parentId===t)var r=n[e].angle;return r}function a(t){for(var e=0;e<n.length;e++)if(n[e].thisId===t)var r=n[e].r;return r}for(let i=0;i<n.length;i++)n[i].thisId!==e&&t.push({start:h(Math.min(n[i].angle,r(n[i].parentId)),"Y"),end:h(Math.max(n[i].angle,r(n[i].parentId)),"Y"),radius:a(n[i].parentId),thisId:n[i].thisId,parentId:n[i].parentId});for(let n=0;n<t.length;n++)Math.sign(t[n].start)!==Math.sign(t[n].end)&&(t[n].end=Math.abs(t[n].end),t[n].start=-Math.abs(t[n].start));return t.filter(n=>n.start!==n.end&0!==n.radius)}(t.data),t},n.readTree=function(n){var t=(n=n.replace(/ \t/g,"")).split(/(;|\(|\)|,)/),e={parent:null,children:[]},r=e,a=0;for(const n of t)if(""!=n&&";"!=n)if("("==n){let n={parent:r,children:[]};r.children.push(n),r=n}else if(","==n){let n={parent:r=r.parent,children:[]};r.children.push(n),r=n}else if(")"==n){if(null===(r=r.parent))break}else{var i=n.split(":");1==i.length?n.startsWith(":")?(r.label="",r.branchLength=parseFloat(i[0])):(r.label=i[0],r.branchLength=null):2==i.length?(r.label=i[0],r.branchLength=parseFloat(i[1])):console.warn(n,"I don't know what to do with two colons!"),r.id=a++}return r.id=a,e},n.rectangleLayout=function(n){var t={};return t.data=s(n),t.vertical_lines=function(n){const t=s(n),e=new Map;for(const n of t){if(null==n.parentId)continue;const t=e.get(n.parentId);t?t.push(n):e.set(n.parentId,[n])}const r=[];for(const[n,t]of e.entries()){if(!t.length)continue;const e=t.map(n=>n.y0),a=Math.min(...e),i=Math.max(...e),l=t[0].x0;r.push({parentId:n,x0:l,x1:l,y0:a,y1:i,heights:i-a})}return r}(n),t},n.subTree=function(n,t){let e={};n.data.forEach(n=>{e[n.thisId]={...n}});let r={};const a=function(n){e[n]&&(r[n]=e[n],e[n].children&&e[n].children.forEach(n=>{a(n)}))};a(t);const i=[["data",Object.values(r)]],l=i[0][1].map(n=>n.thisId);var h=[];for(const t in n)"data"!==t&&h.push([t,n[t]]);var s=h.map(n=>[n[0],n[1].filter(n=>l.includes(n.thisId))]);return Object.fromEntries(i.concat(s))},n.unrooted=function(n){var t={},e=r(function n(t){null===t.parent&&(t.start=0,t.end=2,t.angle=0,t.ntips=a(t),t.x=0,t.y=0);for(var e,r,i=t.start,l=0;l<t.children.length;l++)(e=t.children[l]).ntips=a(e),r=(t.end-t.start)*e.ntips/t.ntips,e.start=i,e.end=e.start+r,e.angle=e.start+(e.end-e.start)/2,i=e.end,e.x=t.x+e.branchLength*Math.sin(e.angle*Math.PI),e.y=t.y+e.branchLength*Math.cos(e.angle*Math.PI),n(e);return t}(n));return t.data=e,t.edges=function(n,t=!1){var e,r=[];n.sort((function(n,t){return n.thisId-t.thisId}));for(const h of n)if(null!==h.parentId&&null!=(e=n[h.parentId]))if(t){var a={x1:h.x,y1:h.y,id1:h.thisId,x2:e.x,y2:h.y,id2:void 0};r.push(a);var i={x1:e.x,y1:h.y,id1:void 0,x2:e.x,y2:e.y,id2:h.parentId};r.push(i)}else{var l={x1:h.x,y1:h.y,id1:h.thisId,x2:e.x,y2:e.y,id2:h.parentId};r.push(l)}return r}(e),t},Object.defineProperty(n,"__esModule",{value:!0})}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@euphrasiologist/lwphylo",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "A lightweight, low level javascript library to plot phylogenies from Newick files.",
|
|
5
5
|
"main": "dist/@euphrasiologist/lwphylo.js",
|
|
6
6
|
"unpkg": "dist/@euphrasiologist/lwphylo.min.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"pretest": "rollup -c",
|
|
18
18
|
"test": "eslint src",
|
|
19
|
-
"prepublishOnly": "rm -rf dist &&
|
|
19
|
+
"prepublishOnly": "rm -rf dist && npm run test"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"rollup": "1",
|
|
39
39
|
"rollup-plugin-terser": "5"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -6,85 +6,46 @@ import fortify from "../utils/fortify.js"
|
|
|
6
6
|
* Attempted copy of .layout.rect() function here https://github.com/ArtPoon/ggfree/blob/master/R/tree.R
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export default function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
pd.map(d => d.
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
export default function(node) {
|
|
10
|
+
const pd = fortify(node);
|
|
11
|
+
|
|
12
|
+
// Fast lookup from id -> pd index
|
|
13
|
+
const idIndex = new Map(pd.map((d, i) => [d.thisId, i]));
|
|
14
|
+
|
|
15
|
+
// 1) Leaf order from the INPUT TREE (respects your child order / ladderize)
|
|
16
|
+
const leafIds = [];
|
|
17
|
+
(function dfs(n) {
|
|
18
|
+
if (!n.children || n.children.length === 0) { leafIds.push(n.id); return; }
|
|
19
|
+
n.children.forEach(dfs);
|
|
20
|
+
})(node);
|
|
21
|
+
|
|
22
|
+
// Map each leaf id to a vertical slot (1..N)
|
|
23
|
+
const tipSlot = new Map(leafIds.map((id, i) => [id, i + 1]));
|
|
24
|
+
|
|
25
|
+
// 2) Set Y for tips directly from that order; compute Y for internal nodes via postorder
|
|
26
|
+
(function setY(n) {
|
|
27
|
+
const i = idIndex.get(n.id);
|
|
28
|
+
if (!n.children || n.children.length === 0) {
|
|
29
|
+
const y = tipSlot.get(n.id);
|
|
30
|
+
pd[i].y0 = y; pd[i].y1 = y;
|
|
31
|
+
return y;
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
for (let i = 0; i < pd.length; i++) {
|
|
41
|
-
if (pd[i].isTip === false) {
|
|
42
|
-
// then y0 === y1 and is the mean of the parental nodes
|
|
43
|
-
pd[i].y0 = yVals(pd[i].children[0], pd[i].children[1]);
|
|
44
|
-
pd[i].y1 = yVals(pd[i].children[0], pd[i].children[1]);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// find root
|
|
49
|
-
var root = pd.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
|
|
50
|
-
|
|
51
|
-
// sort the data temporarily in decreasing parentId
|
|
52
|
-
pd.sort((a, b) => b.thisId - a.thisId);
|
|
53
|
-
|
|
54
|
-
// get the branchlength of the parentID
|
|
55
|
-
function getParentBranchLength(current_parentId) {
|
|
56
|
-
for (var i = 0; i < pd.length; i++) {
|
|
57
|
-
if (pd[i].thisId === current_parentId) {
|
|
58
|
-
var branchLength = pd[i].x1;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return branchLength;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// last loop...
|
|
65
|
-
// now get the x0 and x1 coordinates.
|
|
66
|
-
for (let i = 0; i < pd.length; i++) {
|
|
67
|
-
// special cases where parent is the root.
|
|
68
|
-
if (pd[i].parentId === root) {
|
|
69
|
-
// x0 = 0 and x1 is branch length
|
|
70
|
-
pd[i].x0 = 0;
|
|
71
|
-
pd[i].x1 = pd[i].branchLength;
|
|
72
|
-
} else {
|
|
73
|
-
// the x0 is that of the parent
|
|
74
|
-
var parent_branchLength = getParentBranchLength(pd[i].parentId);
|
|
75
|
-
pd[i].x0 = parent_branchLength;
|
|
76
|
-
// the x1 is the sum of parent and current branchlength
|
|
77
|
-
pd[i].x1 = parent_branchLength + pd[i].branchLength;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// return the original sorted data
|
|
82
|
-
pd.sort((a, b) => a.thisId - b.thisId);
|
|
83
|
-
|
|
84
|
-
// remove root?
|
|
85
|
-
|
|
86
|
-
// finally get rid of unwanted y, x and angle properties
|
|
87
|
-
/* eslint-disable no-unused-vars */
|
|
33
|
+
const ys = n.children.map(setY);
|
|
34
|
+
const y = mean(ys);
|
|
35
|
+
pd[i].y0 = y; pd[i].y1 = y;
|
|
36
|
+
return y;
|
|
37
|
+
})(node);
|
|
38
|
+
|
|
39
|
+
// 3) Set X by accumulating branch lengths down the tree (no sorting-by-id needed)
|
|
40
|
+
(function setX(n, xParent) {
|
|
41
|
+
const i = idIndex.get(n.id);
|
|
42
|
+
const bl = pd[i].branchLength ?? 0;
|
|
43
|
+
const x0 = xParent ?? 0;
|
|
44
|
+
const x1 = x0 + bl;
|
|
45
|
+
pd[i].x0 = x0; pd[i].x1 = x1;
|
|
46
|
+
if (n.children && n.children.length) n.children.forEach(c => setX(c, x1));
|
|
47
|
+
})(node, 0);
|
|
48
|
+
|
|
49
|
+
// Clean up and return
|
|
88
50
|
return pd.map(({ y, x, angle, ...item }) => item);
|
|
89
|
-
|
|
90
|
-
}
|
|
51
|
+
}
|
|
@@ -1,41 +1,35 @@
|
|
|
1
1
|
import getHorizontal from "./getHorizontal.js"
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Get the vertical lines to draw.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
3
|
export default function (node) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return height;
|
|
4
|
+
const data = getHorizontal(node);
|
|
5
|
+
|
|
6
|
+
// Group rows by parentId (children that share a parent)
|
|
7
|
+
const byParent = new Map();
|
|
8
|
+
for (const row of data) {
|
|
9
|
+
if (row.parentId == null) continue;
|
|
10
|
+
const a = byParent.get(row.parentId);
|
|
11
|
+
if (a) a.push(row); else byParent.set(row.parentId, [row]);
|
|
19
12
|
}
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
const verticals = [];
|
|
15
|
+
for (const [parentId, kids] of byParent.entries()) {
|
|
16
|
+
if (!kids.length) continue;
|
|
17
|
+
// Works for binary and multifurcations:
|
|
18
|
+
const yvals = kids.map(d => d.y0);
|
|
19
|
+
const y0 = Math.min(...yvals);
|
|
20
|
+
const y1 = Math.max(...yvals);
|
|
21
|
+
// All children share the same junction x (their x0)
|
|
22
|
+
const x = kids[0].x0;
|
|
23
|
+
|
|
24
|
+
verticals.push({
|
|
25
|
+
parentId,
|
|
26
|
+
x0: x,
|
|
27
|
+
x1: x,
|
|
28
|
+
y0,
|
|
29
|
+
y1,
|
|
30
|
+
heights: y1 - y0
|
|
31
|
+
});
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
return verticals;
|
|
41
|
-
}
|
|
35
|
+
}
|