react-msaview 4.4.6 → 4.5.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.
Files changed (118) hide show
  1. package/bundle/index.js +9 -9
  2. package/bundle/index.js.LICENSE.txt +8 -8
  3. package/bundle/index.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +0 -6
  5. package/dist/colorSchemes.js +1 -119
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/ConservationTrack.d.ts +8 -0
  8. package/dist/components/ConservationTrack.js +54 -0
  9. package/dist/components/ConservationTrack.js.map +1 -0
  10. package/dist/components/Loading.js +14 -2
  11. package/dist/components/Loading.js.map +1 -1
  12. package/dist/components/MSAView.js +36 -0
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/SequenceTextArea.js +3 -2
  15. package/dist/components/SequenceTextArea.js.map +1 -1
  16. package/dist/components/TextTrack.d.ts +3 -3
  17. package/dist/components/TextTrack.js +4 -1
  18. package/dist/components/TextTrack.js.map +1 -1
  19. package/dist/components/Track.js +21 -8
  20. package/dist/components/Track.js.map +1 -1
  21. package/dist/components/dialogs/ExportSVGDialog.js +19 -3
  22. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  23. package/dist/components/header/GappynessSlider.d.ts +6 -0
  24. package/dist/components/header/GappynessSlider.js +19 -0
  25. package/dist/components/header/GappynessSlider.js.map +1 -0
  26. package/dist/components/header/Header.js +3 -1
  27. package/dist/components/header/Header.js.map +1 -1
  28. package/dist/components/header/HeaderMenu.js +30 -14
  29. package/dist/components/header/HeaderMenu.js.map +1 -1
  30. package/dist/components/minimap/MinimapSVG.js +4 -3
  31. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  32. package/dist/components/msa/MSACanvasBlock.js +56 -42
  33. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  34. package/dist/components/msa/renderMSABlock.js +53 -10
  35. package/dist/components/msa/renderMSABlock.js.map +1 -1
  36. package/dist/components/tracks/renderTracksSvg.d.ts +29 -0
  37. package/dist/components/tracks/renderTracksSvg.js +83 -0
  38. package/dist/components/tracks/renderTracksSvg.js.map +1 -0
  39. package/dist/components/tree/TreeNodeMenu.js +2 -2
  40. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  41. package/dist/components/tree/renderTreeCanvas.js +1 -1
  42. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  43. package/dist/constants.d.ts +22 -0
  44. package/dist/constants.js +26 -0
  45. package/dist/constants.js.map +1 -0
  46. package/dist/layout.js.map +1 -1
  47. package/dist/model/msaModel.js +3 -2
  48. package/dist/model/msaModel.js.map +1 -1
  49. package/dist/model/treeModel.js +9 -8
  50. package/dist/model/treeModel.js.map +1 -1
  51. package/dist/model.d.ts +256 -15
  52. package/dist/model.js +408 -128
  53. package/dist/model.js.map +1 -1
  54. package/dist/neighborJoining.d.ts +1 -0
  55. package/dist/neighborJoining.js +839 -0
  56. package/dist/neighborJoining.js.map +1 -0
  57. package/dist/neighborJoining.test.d.ts +1 -0
  58. package/dist/neighborJoining.test.js +110 -0
  59. package/dist/neighborJoining.test.js.map +1 -0
  60. package/dist/parsers/A3mMSA.d.ts +43 -0
  61. package/dist/parsers/A3mMSA.js +277 -0
  62. package/dist/parsers/A3mMSA.js.map +1 -0
  63. package/dist/parsers/A3mMSA.test.d.ts +1 -0
  64. package/dist/parsers/A3mMSA.test.js +138 -0
  65. package/dist/parsers/A3mMSA.test.js.map +1 -0
  66. package/dist/parsers/ClustalMSA.d.ts +4 -4
  67. package/dist/parsers/ClustalMSA.js +3 -1
  68. package/dist/parsers/ClustalMSA.js.map +1 -1
  69. package/dist/parsers/FastaMSA.js +17 -16
  70. package/dist/parsers/FastaMSA.js.map +1 -1
  71. package/dist/renderToSvg.d.ts +1 -0
  72. package/dist/renderToSvg.js +48 -18
  73. package/dist/renderToSvg.js.map +1 -1
  74. package/dist/rowCoordinateCalculations.js +2 -0
  75. package/dist/rowCoordinateCalculations.js.map +1 -1
  76. package/dist/types.d.ts +2 -3
  77. package/dist/util.js +17 -9
  78. package/dist/util.js.map +1 -1
  79. package/dist/version.d.ts +1 -1
  80. package/dist/version.js +1 -1
  81. package/package.json +6 -6
  82. package/src/colorSchemes.ts +1 -179
  83. package/src/components/ConservationTrack.tsx +104 -0
  84. package/src/components/Loading.tsx +44 -2
  85. package/src/components/MSAView.tsx +68 -0
  86. package/src/components/SequenceTextArea.tsx +3 -2
  87. package/src/components/TextTrack.tsx +7 -4
  88. package/src/components/Track.tsx +25 -9
  89. package/src/components/dialogs/ExportSVGDialog.tsx +25 -1
  90. package/src/components/header/GappynessSlider.tsx +35 -0
  91. package/src/components/header/Header.tsx +3 -1
  92. package/src/components/header/HeaderMenu.tsx +36 -15
  93. package/src/components/minimap/MinimapSVG.tsx +6 -3
  94. package/src/components/msa/MSACanvasBlock.tsx +66 -48
  95. package/src/components/msa/renderMSABlock.ts +82 -22
  96. package/src/components/tracks/renderTracksSvg.ts +157 -0
  97. package/src/components/tree/TreeNodeMenu.tsx +2 -2
  98. package/src/components/tree/renderTreeCanvas.ts +1 -1
  99. package/src/constants.ts +27 -0
  100. package/src/layout.ts +1 -6
  101. package/src/model/msaModel.ts +4 -2
  102. package/src/model/treeModel.ts +19 -8
  103. package/src/model.ts +496 -140
  104. package/src/neighborJoining.test.ts +129 -0
  105. package/src/neighborJoining.ts +885 -0
  106. package/src/parsers/A3mMSA.test.ts +164 -0
  107. package/src/parsers/A3mMSA.ts +321 -0
  108. package/src/parsers/ClustalMSA.ts +7 -5
  109. package/src/parsers/FastaMSA.ts +17 -17
  110. package/src/renderToSvg.tsx +105 -26
  111. package/src/rowCoordinateCalculations.ts +2 -0
  112. package/src/types.ts +2 -4
  113. package/src/util.ts +21 -8
  114. package/src/version.ts +1 -1
  115. package/dist/components/dialogs/TracklistDialog.d.ts +0 -7
  116. package/dist/components/dialogs/TracklistDialog.js +0 -23
  117. package/dist/components/dialogs/TracklistDialog.js.map +0 -1
  118. package/src/components/dialogs/TracklistDialog.tsx +0 -73
@@ -0,0 +1,839 @@
1
+ // Neighbor Joining tree construction using BLOSUM62 distances
2
+ // Based on Saitou & Nei (1987) "The neighbor-joining method"
3
+ const BLOSUM62 = {
4
+ A: {
5
+ A: 4,
6
+ R: -1,
7
+ N: -2,
8
+ D: -2,
9
+ C: 0,
10
+ Q: -1,
11
+ E: -1,
12
+ G: 0,
13
+ H: -2,
14
+ I: -1,
15
+ L: -1,
16
+ K: -1,
17
+ M: -1,
18
+ F: -2,
19
+ P: -1,
20
+ S: 1,
21
+ T: 0,
22
+ W: -3,
23
+ Y: -2,
24
+ V: 0,
25
+ B: -2,
26
+ Z: -1,
27
+ X: 0,
28
+ '*': -4,
29
+ },
30
+ R: {
31
+ A: -1,
32
+ R: 5,
33
+ N: 0,
34
+ D: -2,
35
+ C: -3,
36
+ Q: 1,
37
+ E: 0,
38
+ G: -2,
39
+ H: 0,
40
+ I: -3,
41
+ L: -2,
42
+ K: 2,
43
+ M: -1,
44
+ F: -3,
45
+ P: -2,
46
+ S: -1,
47
+ T: -1,
48
+ W: -3,
49
+ Y: -2,
50
+ V: -3,
51
+ B: -1,
52
+ Z: 0,
53
+ X: -1,
54
+ '*': -4,
55
+ },
56
+ N: {
57
+ A: -2,
58
+ R: 0,
59
+ N: 6,
60
+ D: 1,
61
+ C: -3,
62
+ Q: 0,
63
+ E: 0,
64
+ G: 0,
65
+ H: 1,
66
+ I: -3,
67
+ L: -3,
68
+ K: 0,
69
+ M: -2,
70
+ F: -3,
71
+ P: -2,
72
+ S: 1,
73
+ T: 0,
74
+ W: -4,
75
+ Y: -2,
76
+ V: -3,
77
+ B: 3,
78
+ Z: 0,
79
+ X: -1,
80
+ '*': -4,
81
+ },
82
+ D: {
83
+ A: -2,
84
+ R: -2,
85
+ N: 1,
86
+ D: 6,
87
+ C: -3,
88
+ Q: 0,
89
+ E: 2,
90
+ G: -1,
91
+ H: -1,
92
+ I: -3,
93
+ L: -4,
94
+ K: -1,
95
+ M: -3,
96
+ F: -3,
97
+ P: -1,
98
+ S: 0,
99
+ T: -1,
100
+ W: -4,
101
+ Y: -3,
102
+ V: -3,
103
+ B: 4,
104
+ Z: 1,
105
+ X: -1,
106
+ '*': -4,
107
+ },
108
+ C: {
109
+ A: 0,
110
+ R: -3,
111
+ N: -3,
112
+ D: -3,
113
+ C: 9,
114
+ Q: -3,
115
+ E: -4,
116
+ G: -3,
117
+ H: -3,
118
+ I: -1,
119
+ L: -1,
120
+ K: -3,
121
+ M: -1,
122
+ F: -2,
123
+ P: -3,
124
+ S: -1,
125
+ T: -1,
126
+ W: -2,
127
+ Y: -2,
128
+ V: -1,
129
+ B: -3,
130
+ Z: -3,
131
+ X: -2,
132
+ '*': -4,
133
+ },
134
+ Q: {
135
+ A: -1,
136
+ R: 1,
137
+ N: 0,
138
+ D: 0,
139
+ C: -3,
140
+ Q: 5,
141
+ E: 2,
142
+ G: -2,
143
+ H: 0,
144
+ I: -3,
145
+ L: -2,
146
+ K: 1,
147
+ M: 0,
148
+ F: -3,
149
+ P: -1,
150
+ S: 0,
151
+ T: -1,
152
+ W: -2,
153
+ Y: -1,
154
+ V: -2,
155
+ B: 0,
156
+ Z: 3,
157
+ X: -1,
158
+ '*': -4,
159
+ },
160
+ E: {
161
+ A: -1,
162
+ R: 0,
163
+ N: 0,
164
+ D: 2,
165
+ C: -4,
166
+ Q: 2,
167
+ E: 5,
168
+ G: -2,
169
+ H: 0,
170
+ I: -3,
171
+ L: -3,
172
+ K: 1,
173
+ M: -2,
174
+ F: -3,
175
+ P: -1,
176
+ S: 0,
177
+ T: -1,
178
+ W: -3,
179
+ Y: -2,
180
+ V: -2,
181
+ B: 1,
182
+ Z: 4,
183
+ X: -1,
184
+ '*': -4,
185
+ },
186
+ G: {
187
+ A: 0,
188
+ R: -2,
189
+ N: 0,
190
+ D: -1,
191
+ C: -3,
192
+ Q: -2,
193
+ E: -2,
194
+ G: 6,
195
+ H: -2,
196
+ I: -4,
197
+ L: -4,
198
+ K: -2,
199
+ M: -3,
200
+ F: -3,
201
+ P: -2,
202
+ S: 0,
203
+ T: -2,
204
+ W: -2,
205
+ Y: -3,
206
+ V: -3,
207
+ B: -1,
208
+ Z: -2,
209
+ X: -1,
210
+ '*': -4,
211
+ },
212
+ H: {
213
+ A: -2,
214
+ R: 0,
215
+ N: 1,
216
+ D: -1,
217
+ C: -3,
218
+ Q: 0,
219
+ E: 0,
220
+ G: -2,
221
+ H: 8,
222
+ I: -3,
223
+ L: -3,
224
+ K: -1,
225
+ M: -2,
226
+ F: -1,
227
+ P: -2,
228
+ S: -1,
229
+ T: -2,
230
+ W: -2,
231
+ Y: 2,
232
+ V: -3,
233
+ B: 0,
234
+ Z: 0,
235
+ X: -1,
236
+ '*': -4,
237
+ },
238
+ I: {
239
+ A: -1,
240
+ R: -3,
241
+ N: -3,
242
+ D: -3,
243
+ C: -1,
244
+ Q: -3,
245
+ E: -3,
246
+ G: -4,
247
+ H: -3,
248
+ I: 4,
249
+ L: 2,
250
+ K: -3,
251
+ M: 1,
252
+ F: 0,
253
+ P: -3,
254
+ S: -2,
255
+ T: -1,
256
+ W: -3,
257
+ Y: -1,
258
+ V: 3,
259
+ B: -3,
260
+ Z: -3,
261
+ X: -1,
262
+ '*': -4,
263
+ },
264
+ L: {
265
+ A: -1,
266
+ R: -2,
267
+ N: -3,
268
+ D: -4,
269
+ C: -1,
270
+ Q: -2,
271
+ E: -3,
272
+ G: -4,
273
+ H: -3,
274
+ I: 2,
275
+ L: 4,
276
+ K: -2,
277
+ M: 2,
278
+ F: 0,
279
+ P: -3,
280
+ S: -2,
281
+ T: -1,
282
+ W: -2,
283
+ Y: -1,
284
+ V: 1,
285
+ B: -4,
286
+ Z: -3,
287
+ X: -1,
288
+ '*': -4,
289
+ },
290
+ K: {
291
+ A: -1,
292
+ R: 2,
293
+ N: 0,
294
+ D: -1,
295
+ C: -3,
296
+ Q: 1,
297
+ E: 1,
298
+ G: -2,
299
+ H: -1,
300
+ I: -3,
301
+ L: -2,
302
+ K: 5,
303
+ M: -1,
304
+ F: -3,
305
+ P: -1,
306
+ S: 0,
307
+ T: -1,
308
+ W: -3,
309
+ Y: -2,
310
+ V: -2,
311
+ B: 0,
312
+ Z: 1,
313
+ X: -1,
314
+ '*': -4,
315
+ },
316
+ M: {
317
+ A: -1,
318
+ R: -1,
319
+ N: -2,
320
+ D: -3,
321
+ C: -1,
322
+ Q: 0,
323
+ E: -2,
324
+ G: -3,
325
+ H: -2,
326
+ I: 1,
327
+ L: 2,
328
+ K: -1,
329
+ M: 5,
330
+ F: 0,
331
+ P: -2,
332
+ S: -1,
333
+ T: -1,
334
+ W: -1,
335
+ Y: -1,
336
+ V: 1,
337
+ B: -3,
338
+ Z: -1,
339
+ X: -1,
340
+ '*': -4,
341
+ },
342
+ F: {
343
+ A: -2,
344
+ R: -3,
345
+ N: -3,
346
+ D: -3,
347
+ C: -2,
348
+ Q: -3,
349
+ E: -3,
350
+ G: -3,
351
+ H: -1,
352
+ I: 0,
353
+ L: 0,
354
+ K: -3,
355
+ M: 0,
356
+ F: 6,
357
+ P: -4,
358
+ S: -2,
359
+ T: -2,
360
+ W: 1,
361
+ Y: 3,
362
+ V: -1,
363
+ B: -3,
364
+ Z: -3,
365
+ X: -1,
366
+ '*': -4,
367
+ },
368
+ P: {
369
+ A: -1,
370
+ R: -2,
371
+ N: -2,
372
+ D: -1,
373
+ C: -3,
374
+ Q: -1,
375
+ E: -1,
376
+ G: -2,
377
+ H: -2,
378
+ I: -3,
379
+ L: -3,
380
+ K: -1,
381
+ M: -2,
382
+ F: -4,
383
+ P: 7,
384
+ S: -1,
385
+ T: -1,
386
+ W: -4,
387
+ Y: -3,
388
+ V: -2,
389
+ B: -2,
390
+ Z: -1,
391
+ X: -2,
392
+ '*': -4,
393
+ },
394
+ S: {
395
+ A: 1,
396
+ R: -1,
397
+ N: 1,
398
+ D: 0,
399
+ C: -1,
400
+ Q: 0,
401
+ E: 0,
402
+ G: 0,
403
+ H: -1,
404
+ I: -2,
405
+ L: -2,
406
+ K: 0,
407
+ M: -1,
408
+ F: -2,
409
+ P: -1,
410
+ S: 4,
411
+ T: 1,
412
+ W: -3,
413
+ Y: -2,
414
+ V: -2,
415
+ B: 0,
416
+ Z: 0,
417
+ X: 0,
418
+ '*': -4,
419
+ },
420
+ T: {
421
+ A: 0,
422
+ R: -1,
423
+ N: 0,
424
+ D: -1,
425
+ C: -1,
426
+ Q: -1,
427
+ E: -1,
428
+ G: -2,
429
+ H: -2,
430
+ I: -1,
431
+ L: -1,
432
+ K: -1,
433
+ M: -1,
434
+ F: -2,
435
+ P: -1,
436
+ S: 1,
437
+ T: 5,
438
+ W: -2,
439
+ Y: -2,
440
+ V: 0,
441
+ B: -1,
442
+ Z: -1,
443
+ X: 0,
444
+ '*': -4,
445
+ },
446
+ W: {
447
+ A: -3,
448
+ R: -3,
449
+ N: -4,
450
+ D: -4,
451
+ C: -2,
452
+ Q: -2,
453
+ E: -3,
454
+ G: -2,
455
+ H: -2,
456
+ I: -3,
457
+ L: -2,
458
+ K: -3,
459
+ M: -1,
460
+ F: 1,
461
+ P: -4,
462
+ S: -3,
463
+ T: -2,
464
+ W: 11,
465
+ Y: 2,
466
+ V: -3,
467
+ B: -4,
468
+ Z: -3,
469
+ X: -2,
470
+ '*': -4,
471
+ },
472
+ Y: {
473
+ A: -2,
474
+ R: -2,
475
+ N: -2,
476
+ D: -3,
477
+ C: -2,
478
+ Q: -1,
479
+ E: -2,
480
+ G: -3,
481
+ H: 2,
482
+ I: -1,
483
+ L: -1,
484
+ K: -2,
485
+ M: -1,
486
+ F: 3,
487
+ P: -3,
488
+ S: -2,
489
+ T: -2,
490
+ W: 2,
491
+ Y: 7,
492
+ V: -1,
493
+ B: -3,
494
+ Z: -2,
495
+ X: -1,
496
+ '*': -4,
497
+ },
498
+ V: {
499
+ A: 0,
500
+ R: -3,
501
+ N: -3,
502
+ D: -3,
503
+ C: -1,
504
+ Q: -2,
505
+ E: -2,
506
+ G: -3,
507
+ H: -3,
508
+ I: 3,
509
+ L: 1,
510
+ K: -2,
511
+ M: 1,
512
+ F: -1,
513
+ P: -2,
514
+ S: -2,
515
+ T: 0,
516
+ W: -3,
517
+ Y: -1,
518
+ V: 4,
519
+ B: -3,
520
+ Z: -2,
521
+ X: -1,
522
+ '*': -4,
523
+ },
524
+ B: {
525
+ A: -2,
526
+ R: -1,
527
+ N: 3,
528
+ D: 4,
529
+ C: -3,
530
+ Q: 0,
531
+ E: 1,
532
+ G: -1,
533
+ H: 0,
534
+ I: -3,
535
+ L: -4,
536
+ K: 0,
537
+ M: -3,
538
+ F: -3,
539
+ P: -2,
540
+ S: 0,
541
+ T: -1,
542
+ W: -4,
543
+ Y: -3,
544
+ V: -3,
545
+ B: 4,
546
+ Z: 1,
547
+ X: -1,
548
+ '*': -4,
549
+ },
550
+ Z: {
551
+ A: -1,
552
+ R: 0,
553
+ N: 0,
554
+ D: 1,
555
+ C: -3,
556
+ Q: 3,
557
+ E: 4,
558
+ G: -2,
559
+ H: 0,
560
+ I: -3,
561
+ L: -3,
562
+ K: 1,
563
+ M: -1,
564
+ F: -3,
565
+ P: -1,
566
+ S: 0,
567
+ T: -1,
568
+ W: -3,
569
+ Y: -2,
570
+ V: -2,
571
+ B: 1,
572
+ Z: 4,
573
+ X: -1,
574
+ '*': -4,
575
+ },
576
+ X: {
577
+ A: 0,
578
+ R: -1,
579
+ N: -1,
580
+ D: -1,
581
+ C: -2,
582
+ Q: -1,
583
+ E: -1,
584
+ G: -1,
585
+ H: -1,
586
+ I: -1,
587
+ L: -1,
588
+ K: -1,
589
+ M: -1,
590
+ F: -1,
591
+ P: -2,
592
+ S: 0,
593
+ T: 0,
594
+ W: -2,
595
+ Y: -1,
596
+ V: -1,
597
+ B: -1,
598
+ Z: -1,
599
+ X: -1,
600
+ '*': -4,
601
+ },
602
+ '*': {
603
+ A: -4,
604
+ R: -4,
605
+ N: -4,
606
+ D: -4,
607
+ C: -4,
608
+ Q: -4,
609
+ E: -4,
610
+ G: -4,
611
+ H: -4,
612
+ I: -4,
613
+ L: -4,
614
+ K: -4,
615
+ M: -4,
616
+ F: -4,
617
+ P: -4,
618
+ S: -4,
619
+ T: -4,
620
+ W: -4,
621
+ Y: -4,
622
+ V: -4,
623
+ B: -4,
624
+ Z: -4,
625
+ X: -4,
626
+ '*': 1,
627
+ },
628
+ };
629
+ function getBlosum62Score(a, b) {
630
+ const upper_a = a.toUpperCase();
631
+ const upper_b = b.toUpperCase();
632
+ return BLOSUM62[upper_a]?.[upper_b] ?? -4;
633
+ }
634
+ function computePairwiseDistance(seq1, seq2) {
635
+ if (seq1.length !== seq2.length) {
636
+ throw new Error('Sequences must have the same length (aligned)');
637
+ }
638
+ let matches = 0;
639
+ let mismatches = 0;
640
+ let totalScore = 0;
641
+ let maxPossibleScore = 0;
642
+ for (let i = 0; i < seq1.length; i++) {
643
+ const a = seq1[i];
644
+ const b = seq2[i];
645
+ if (a === '-' && b === '-') {
646
+ continue;
647
+ }
648
+ if (a === '-' || b === '-') {
649
+ mismatches++;
650
+ continue;
651
+ }
652
+ const score = getBlosum62Score(a, b);
653
+ totalScore += score;
654
+ maxPossibleScore += Math.max(getBlosum62Score(a, a), getBlosum62Score(b, b));
655
+ if (a.toUpperCase() === b.toUpperCase()) {
656
+ matches++;
657
+ }
658
+ else {
659
+ mismatches++;
660
+ }
661
+ }
662
+ const total = matches + mismatches;
663
+ if (total === 0) {
664
+ return 1;
665
+ }
666
+ // Convert similarity to distance using normalized BLOSUM62 score
667
+ // Higher scores mean more similar, so we invert
668
+ if (maxPossibleScore <= 0) {
669
+ return 1;
670
+ }
671
+ const normalizedScore = totalScore / maxPossibleScore;
672
+ // Clamp to valid range and convert to distance
673
+ const clampedScore = Math.max(0.01, Math.min(1, normalizedScore));
674
+ // Use Kimura-like correction: d = -ln(similarity)
675
+ return -Math.log(clampedScore);
676
+ }
677
+ function computeDistanceMatrix(rows) {
678
+ const n = rows.length;
679
+ const distances = [];
680
+ for (let i = 0; i < n; i++) {
681
+ distances[i] = [];
682
+ for (let j = 0; j < n; j++) {
683
+ if (i === j) {
684
+ distances[i][j] = 0;
685
+ }
686
+ else if (j < i) {
687
+ distances[i][j] = distances[j][i];
688
+ }
689
+ else {
690
+ distances[i][j] = computePairwiseDistance(rows[i][1], rows[j][1]);
691
+ }
692
+ }
693
+ }
694
+ return distances;
695
+ }
696
+ function neighborJoining(distances, names) {
697
+ const n = distances.length;
698
+ if (n < 2) {
699
+ return { name: names[0] };
700
+ }
701
+ if (n === 2) {
702
+ const d = distances[0][1];
703
+ return {
704
+ left: { name: names[0] },
705
+ right: { name: names[1] },
706
+ leftLength: d / 2,
707
+ rightLength: d / 2,
708
+ };
709
+ }
710
+ // Work with copies that we'll modify
711
+ const D = [];
712
+ for (let i = 0; i < n; i++) {
713
+ D[i] = [...distances[i]];
714
+ }
715
+ const nodes = names.map(name => ({ name }));
716
+ let remaining = n;
717
+ while (remaining > 2) {
718
+ // Find active indices
719
+ const active = [];
720
+ for (let i = 0; i < nodes.length; i++) {
721
+ if (nodes[i] !== undefined) {
722
+ active.push(i);
723
+ }
724
+ }
725
+ // Compute r values (sum of distances for each node)
726
+ const r = new Map();
727
+ for (const i of active) {
728
+ let sum = 0;
729
+ for (const j of active) {
730
+ if (i !== j) {
731
+ sum += D[i][j];
732
+ }
733
+ }
734
+ r.set(i, sum);
735
+ }
736
+ // Find pair with minimum Q value
737
+ let minQ = Infinity;
738
+ let minI = -1;
739
+ let minJ = -1;
740
+ for (let ai = 0; ai < active.length; ai++) {
741
+ for (let aj = ai + 1; aj < active.length; aj++) {
742
+ const i = active[ai];
743
+ const j = active[aj];
744
+ const q = (remaining - 2) * D[i][j] - r.get(i) - r.get(j);
745
+ if (q < minQ) {
746
+ minQ = q;
747
+ minI = i;
748
+ minJ = j;
749
+ }
750
+ }
751
+ }
752
+ // Calculate branch lengths
753
+ const dij = D[minI][minJ];
754
+ const ri = r.get(minI);
755
+ const rj = r.get(minJ);
756
+ let limbI;
757
+ let limbJ;
758
+ if (remaining > 2) {
759
+ limbI = dij / 2 + (ri - rj) / (2 * (remaining - 2));
760
+ limbJ = dij - limbI;
761
+ }
762
+ else {
763
+ limbI = dij / 2;
764
+ limbJ = dij / 2;
765
+ }
766
+ // Ensure non-negative branch lengths
767
+ limbI = Math.max(0, limbI);
768
+ limbJ = Math.max(0, limbJ);
769
+ // Create new node
770
+ const newNode = {
771
+ left: nodes[minI],
772
+ right: nodes[minJ],
773
+ leftLength: limbI,
774
+ rightLength: limbJ,
775
+ };
776
+ // Update distance matrix
777
+ const newIdx = minI;
778
+ for (const k of active) {
779
+ if (k !== minI && k !== minJ) {
780
+ const newDist = (D[minI][k] + D[minJ][k] - dij) / 2;
781
+ D[newIdx][k] = Math.max(0, newDist);
782
+ D[k][newIdx] = Math.max(0, newDist);
783
+ }
784
+ }
785
+ // Mark minJ as removed
786
+ nodes[minJ] = undefined;
787
+ nodes[newIdx] = newNode;
788
+ remaining--;
789
+ }
790
+ // Connect final two nodes
791
+ const finalActive = [];
792
+ for (let i = 0; i < nodes.length; i++) {
793
+ if (nodes[i] !== undefined) {
794
+ finalActive.push(i);
795
+ }
796
+ }
797
+ if (finalActive.length === 2) {
798
+ const i = finalActive[0];
799
+ const j = finalActive[1];
800
+ const d = D[i][j];
801
+ return {
802
+ left: nodes[i],
803
+ right: nodes[j],
804
+ leftLength: d / 2,
805
+ rightLength: d / 2,
806
+ };
807
+ }
808
+ return nodes[finalActive[0]];
809
+ }
810
+ function nodeToNewick(node, branchLength) {
811
+ let result;
812
+ if (node.name !== undefined && !node.left && !node.right) {
813
+ // Leaf node - escape special characters in name
814
+ const escapedName = node.name.replace(/[():,;[\]]/g, '_');
815
+ result = escapedName;
816
+ }
817
+ else {
818
+ // Internal node
819
+ const leftNewick = node.left ? nodeToNewick(node.left, node.leftLength) : '';
820
+ const rightNewick = node.right
821
+ ? nodeToNewick(node.right, node.rightLength)
822
+ : '';
823
+ result = `(${leftNewick},${rightNewick})`;
824
+ }
825
+ if (branchLength !== undefined) {
826
+ result += `:${branchLength.toFixed(6)}`;
827
+ }
828
+ return result;
829
+ }
830
+ export function calculateNeighborJoiningTree(rows) {
831
+ if (rows.length < 2) {
832
+ throw new Error('Need at least 2 sequences to build a tree');
833
+ }
834
+ const names = rows.map(r => r[0]);
835
+ const distances = computeDistanceMatrix(rows);
836
+ const tree = neighborJoining(distances, names);
837
+ return nodeToNewick(tree) + ';';
838
+ }
839
+ //# sourceMappingURL=neighborJoining.js.map