react-msaview 4.3.0 → 4.4.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/bundle/index.js +15 -15
- package/bundle/index.js.LICENSE.txt +1 -9
- package/bundle/index.js.map +1 -1
- package/dist/colorSchemes.js +2 -2
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +2 -2
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/SettingsDialog.js +1 -1
- package/dist/components/dialogs/SettingsDialog.js.map +1 -1
- package/dist/components/header/{SettingsMenu.d.ts → ColorMenu.d.ts} +2 -2
- package/dist/components/header/ColorMenu.js +19 -0
- package/dist/components/header/ColorMenu.js.map +1 -0
- package/dist/components/header/Header.js +6 -2
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +3 -2
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/HeaderMenu.js +15 -97
- package/dist/components/header/HeaderMenu.js.map +1 -1
- package/dist/components/header/MSAMenu.d.ts +6 -0
- package/dist/components/header/MSAMenu.js +44 -0
- package/dist/components/header/MSAMenu.js.map +1 -0
- package/dist/components/header/TreeMenu.d.ts +6 -0
- package/dist/components/header/TreeMenu.js +64 -0
- package/dist/components/header/TreeMenu.js.map +1 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +4 -4
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +13 -9
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/model.d.ts +7 -1
- package/dist/model.js +54 -53
- package/dist/model.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +13 -2
- package/dist/rowCoordinateCalculations.js +60 -17
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/rowCoordinateCalculations.test.js +96 -2
- package/dist/rowCoordinateCalculations.test.js.map +1 -1
- package/dist/seqCoordToRowSpecificGlobalCoord.d.ts +4 -0
- package/dist/seqCoordToRowSpecificGlobalCoord.js +15 -0
- package/dist/seqCoordToRowSpecificGlobalCoord.js.map +1 -0
- package/dist/seqCoordToRowSpecificGlobalCoord.test.d.ts +1 -0
- package/dist/seqCoordToRowSpecificGlobalCoord.test.js +42 -0
- package/dist/seqCoordToRowSpecificGlobalCoord.test.js.map +1 -0
- package/dist/util.d.ts +1 -6
- package/dist/util.js +5 -22
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/colorSchemes.ts +2 -2
- package/src/components/VerticalScrollbar.tsx +2 -3
- package/src/components/dialogs/SettingsDialog.tsx +1 -1
- package/src/components/header/ColorMenu.tsx +33 -0
- package/src/components/header/Header.tsx +6 -2
- package/src/components/header/HeaderInfoArea.tsx +5 -2
- package/src/components/header/HeaderMenu.tsx +15 -110
- package/src/components/header/MSAMenu.tsx +55 -0
- package/src/components/header/TreeMenu.tsx +82 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +4 -4
- package/src/components/msa/renderMSABlock.ts +26 -22
- package/src/model.ts +73 -61
- package/src/rowCoordinateCalculations.test.ts +138 -2
- package/src/rowCoordinateCalculations.ts +95 -18
- package/src/seqCoordToRowSpecificGlobalCoord.test.ts +53 -0
- package/src/seqCoordToRowSpecificGlobalCoord.ts +20 -0
- package/src/util.ts +5 -28
- package/src/version.ts +1 -1
- package/dist/components/header/SettingsMenu.js +0 -141
- package/dist/components/header/SettingsMenu.js.map +0 -1
- package/src/components/header/SettingsMenu.tsx +0 -169
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
|
+
import AccountTree from '@mui/icons-material/AccountTree'
|
|
5
|
+
import { observer } from 'mobx-react'
|
|
6
|
+
|
|
7
|
+
import type { MsaViewModel } from '../../model'
|
|
8
|
+
|
|
9
|
+
const TreeMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
10
|
+
const {
|
|
11
|
+
drawTree,
|
|
12
|
+
showBranchLen,
|
|
13
|
+
labelsAlignRight,
|
|
14
|
+
drawNodeBubbles,
|
|
15
|
+
drawLabels,
|
|
16
|
+
treeWidthMatchesArea,
|
|
17
|
+
noTree,
|
|
18
|
+
} = model
|
|
19
|
+
return (
|
|
20
|
+
<CascadingMenuButton
|
|
21
|
+
closeAfterItemClick={false}
|
|
22
|
+
menuItems={[
|
|
23
|
+
{
|
|
24
|
+
label: 'Show branch length',
|
|
25
|
+
type: 'checkbox',
|
|
26
|
+
checked: showBranchLen,
|
|
27
|
+
onClick: () => {
|
|
28
|
+
model.setShowBranchLen(!showBranchLen)
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
label: 'Show tree',
|
|
33
|
+
type: 'checkbox',
|
|
34
|
+
checked: drawTree,
|
|
35
|
+
onClick: () => {
|
|
36
|
+
model.setDrawTree(!drawTree)
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: 'Draw clickable bubbles on tree branches',
|
|
41
|
+
type: 'checkbox',
|
|
42
|
+
checked: drawNodeBubbles,
|
|
43
|
+
onClick: () => {
|
|
44
|
+
model.setDrawNodeBubbles(!drawNodeBubbles)
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Tree labels align right',
|
|
49
|
+
type: 'checkbox',
|
|
50
|
+
checked: labelsAlignRight,
|
|
51
|
+
onClick: () => {
|
|
52
|
+
model.setLabelsAlignRight(!labelsAlignRight)
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Draw labels',
|
|
57
|
+
type: 'checkbox',
|
|
58
|
+
checked: drawLabels,
|
|
59
|
+
onClick: () => {
|
|
60
|
+
model.setDrawLabels(!drawLabels)
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
...(noTree
|
|
64
|
+
? []
|
|
65
|
+
: [
|
|
66
|
+
{
|
|
67
|
+
label: 'Make tree width fit to tree area',
|
|
68
|
+
type: 'checkbox' as const,
|
|
69
|
+
checked: treeWidthMatchesArea,
|
|
70
|
+
onClick: () => {
|
|
71
|
+
model.setTreeWidthMatchesArea(!treeWidthMatchesArea)
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
]),
|
|
75
|
+
]}
|
|
76
|
+
>
|
|
77
|
+
<AccountTree />
|
|
78
|
+
</CascadingMenuButton>
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export default TreeMenu
|
|
@@ -56,7 +56,8 @@ function drawTiles({
|
|
|
56
56
|
tidyFilteredGatheredInterProAnnotations,
|
|
57
57
|
} = model
|
|
58
58
|
|
|
59
|
-
for (
|
|
59
|
+
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
60
|
+
const node = visibleLeaves[i]!
|
|
60
61
|
const {
|
|
61
62
|
x,
|
|
62
63
|
data: { name },
|
|
@@ -65,9 +66,9 @@ function drawTiles({
|
|
|
65
66
|
|
|
66
67
|
const entry = tidyFilteredGatheredInterProAnnotations[name]
|
|
67
68
|
|
|
68
|
-
let j = 0
|
|
69
69
|
if (entry) {
|
|
70
|
-
for (
|
|
70
|
+
for (let j = 0, l2 = entry.length; j < l2; j++) {
|
|
71
|
+
const { start, end, accession } = entry[j]!
|
|
71
72
|
const m1 = model.seqCoordToRowSpecificGlobalCoord(name, start - 1)
|
|
72
73
|
const m2 = model.seqCoordToRowSpecificGlobalCoord(name, end)
|
|
73
74
|
const x = m1 * colWidth
|
|
@@ -78,7 +79,6 @@ function drawTiles({
|
|
|
78
79
|
const lw = colWidth * (m2 - m1)
|
|
79
80
|
ctx.fillRect(x, t, lw, h)
|
|
80
81
|
ctx.strokeRect(x, t, lw, h)
|
|
81
|
-
j++
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -34,6 +34,7 @@ export function renderMSABlock({
|
|
|
34
34
|
highResScaleFactor,
|
|
35
35
|
actuallyShowDomains,
|
|
36
36
|
leaves,
|
|
37
|
+
bgColor,
|
|
37
38
|
} = model
|
|
38
39
|
const k = highResScaleFactorOverride || highResScaleFactor
|
|
39
40
|
const bx = blockSizeXOverride || blockSize
|
|
@@ -42,7 +43,7 @@ export function renderMSABlock({
|
|
|
42
43
|
ctx.scale(k, k)
|
|
43
44
|
ctx.translate(-offsetX, rowHeight / 2 - offsetY)
|
|
44
45
|
ctx.textAlign = 'center'
|
|
45
|
-
ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
|
|
46
|
+
ctx.font = ctx.font.replace(/\d+px/, `${bgColor ? '' : 'bold '}${fontSize}px`)
|
|
46
47
|
|
|
47
48
|
const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
|
|
48
49
|
const yEnd = Math.max(0, Math.ceil((offsetY + by + rowHeight) / rowHeight))
|
|
@@ -102,38 +103,40 @@ function drawTiles({
|
|
|
102
103
|
rowHeight,
|
|
103
104
|
} = model
|
|
104
105
|
|
|
105
|
-
for (
|
|
106
|
+
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
107
|
+
const node = visibleLeaves[i]!
|
|
106
108
|
const {
|
|
107
109
|
data: { name },
|
|
108
110
|
} = node
|
|
109
111
|
const y = node.x!
|
|
110
112
|
const str = columns[name]?.slice(xStart, xEnd)
|
|
111
113
|
if (str) {
|
|
112
|
-
for (let i = 0
|
|
114
|
+
for (let i = 0, l2 = str.length; i < l2; i++) {
|
|
113
115
|
const letter = str[i]!
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
const r1 = colorSchemeName === 'clustalx_protein_dynamic'
|
|
117
|
+
const r2 = colorSchemeName === 'percent_identity_dynamic'
|
|
118
|
+
const color = r1
|
|
119
|
+
? getClustalXColor(
|
|
120
|
+
// use model.colStats dot notation here: delay use of colStats
|
|
121
|
+
// until absolutely needed
|
|
122
|
+
model.colStats[xStart + i]!,
|
|
123
|
+
model.colStatsSums[xStart + i]!,
|
|
124
|
+
model,
|
|
125
|
+
name,
|
|
126
|
+
xStart + i,
|
|
127
|
+
)
|
|
128
|
+
: r2
|
|
129
|
+
? getPercentIdentityColor(
|
|
130
|
+
// use model.colStats dot notation here: delay use of
|
|
131
|
+
// colStats until absolutely needed
|
|
119
132
|
model.colStats[xStart + i]!,
|
|
120
133
|
model.colStatsSums[xStart + i]!,
|
|
121
134
|
model,
|
|
122
135
|
name,
|
|
123
136
|
xStart + i,
|
|
124
137
|
)
|
|
125
|
-
:
|
|
126
|
-
|
|
127
|
-
// use model.colStats dot notation here: delay use of
|
|
128
|
-
// colStats until absolutely needed
|
|
129
|
-
model.colStats[xStart + i]!,
|
|
130
|
-
model.colStatsSums[xStart + i]!,
|
|
131
|
-
model,
|
|
132
|
-
name,
|
|
133
|
-
xStart + i,
|
|
134
|
-
)
|
|
135
|
-
: colorScheme[letter.toUpperCase()]
|
|
136
|
-
if (bgColor) {
|
|
138
|
+
: colorScheme[letter.toUpperCase()]
|
|
139
|
+
if (bgColor || r1 || r2) {
|
|
137
140
|
ctx.fillStyle = color || theme.palette.background.default
|
|
138
141
|
ctx.fillRect(
|
|
139
142
|
i * colWidth + offsetX - (offsetX % colWidth),
|
|
@@ -176,14 +179,15 @@ function drawText({
|
|
|
176
179
|
rowHeight,
|
|
177
180
|
} = model
|
|
178
181
|
if (showMsaLetters) {
|
|
179
|
-
for (
|
|
182
|
+
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
183
|
+
const node = visibleLeaves[i]!
|
|
180
184
|
const {
|
|
181
185
|
data: { name },
|
|
182
186
|
} = node
|
|
183
187
|
const y = node.x!
|
|
184
188
|
const str = columns[name]?.slice(xStart, xEnd)
|
|
185
189
|
if (str) {
|
|
186
|
-
for (let i = 0
|
|
190
|
+
for (let i = 0, l2 = str.length; i < l2; i++) {
|
|
187
191
|
const letter = str[i]!
|
|
188
192
|
const color = colorScheme[letter.toUpperCase()]
|
|
189
193
|
const contrast = contrastLettering
|
package/src/model.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
clamp,
|
|
3
|
+
fetchAndMaybeUnzipText,
|
|
4
|
+
groupBy,
|
|
5
|
+
localStorageGetBoolean,
|
|
6
|
+
localStorageSetBoolean,
|
|
7
|
+
notEmpty,
|
|
8
|
+
sum,
|
|
9
|
+
} from '@jbrowse/core/util'
|
|
2
10
|
import { openLocation } from '@jbrowse/core/util/io'
|
|
3
11
|
import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst'
|
|
4
12
|
import { colord } from 'colord'
|
|
@@ -8,7 +16,6 @@ import { parseEmfTree } from 'emf-js'
|
|
|
8
16
|
import { saveAs } from 'file-saver'
|
|
9
17
|
import { autorun, transaction } from 'mobx'
|
|
10
18
|
import { addDisposer, cast, types } from 'mobx-state-tree'
|
|
11
|
-
import { ungzip } from 'pako'
|
|
12
19
|
import Stockholm from 'stockholm-js'
|
|
13
20
|
|
|
14
21
|
import { blocksX, blocksY } from './calculateBlocks'
|
|
@@ -29,17 +36,15 @@ import FastaMSA from './parsers/FastaMSA'
|
|
|
29
36
|
import StockholmMSA from './parsers/StockholmMSA'
|
|
30
37
|
import { reparseTree } from './reparseTree'
|
|
31
38
|
import {
|
|
32
|
-
|
|
39
|
+
mouseOverCoordToGapRemovedRowCoord,
|
|
33
40
|
mouseOverCoordToGlobalCoord,
|
|
34
41
|
} from './rowCoordinateCalculations'
|
|
42
|
+
import { seqCoordToRowSpecificGlobalCoord } from './seqCoordToRowSpecificGlobalCoord'
|
|
35
43
|
import {
|
|
36
|
-
clamp,
|
|
37
44
|
collapse,
|
|
38
45
|
generateNodeIds,
|
|
39
|
-
|
|
46
|
+
isBlank,
|
|
40
47
|
len,
|
|
41
|
-
localStorageGetBoolean,
|
|
42
|
-
localStorageSetBoolean,
|
|
43
48
|
maxLength,
|
|
44
49
|
setBrLength,
|
|
45
50
|
skipBlanks,
|
|
@@ -627,24 +632,23 @@ function stateModelFactory() {
|
|
|
627
632
|
*/
|
|
628
633
|
get tree(): NodeWithIds {
|
|
629
634
|
const text = self.data.tree
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
635
|
+
|
|
636
|
+
return reparseTree(
|
|
637
|
+
text
|
|
638
|
+
? generateNodeIds(
|
|
633
639
|
text.startsWith('BioTreeContainer')
|
|
634
640
|
? flatToTree(parseAsn1(text))
|
|
635
641
|
: parseNewick(
|
|
636
642
|
text.startsWith('SEQ') ? parseEmfTree(text).tree : text,
|
|
637
643
|
),
|
|
638
|
-
)
|
|
639
|
-
)
|
|
640
|
-
: reparseTree(
|
|
641
|
-
this.MSA?.getTree() || {
|
|
644
|
+
)
|
|
645
|
+
: this.MSA?.getTree() || {
|
|
642
646
|
noTree: true,
|
|
643
647
|
children: [],
|
|
644
648
|
id: 'empty',
|
|
645
649
|
name: 'empty',
|
|
646
650
|
},
|
|
647
|
-
|
|
651
|
+
)
|
|
648
652
|
},
|
|
649
653
|
|
|
650
654
|
/**
|
|
@@ -711,16 +715,18 @@ function stateModelFactory() {
|
|
|
711
715
|
if (hideGaps) {
|
|
712
716
|
const strs = this.leaves
|
|
713
717
|
.map(leaf => this.MSA?.getRow(leaf.data.name))
|
|
714
|
-
.filter(
|
|
718
|
+
.filter(notEmpty)
|
|
715
719
|
if (strs.length) {
|
|
716
|
-
|
|
720
|
+
const s0len = strs[0]!.length
|
|
721
|
+
for (let i = 0; i < s0len; i++) {
|
|
717
722
|
let counter = 0
|
|
718
|
-
|
|
719
|
-
|
|
723
|
+
const l = strs.length
|
|
724
|
+
for (let j = 0; j < l; j++) {
|
|
725
|
+
if (isBlank(strs[j]![i])) {
|
|
720
726
|
counter++
|
|
721
727
|
}
|
|
722
728
|
}
|
|
723
|
-
if (counter /
|
|
729
|
+
if (counter / l >= realAllowedGappyness / 100) {
|
|
724
730
|
blanks.push(i)
|
|
725
731
|
}
|
|
726
732
|
}
|
|
@@ -948,14 +954,14 @@ function stateModelFactory() {
|
|
|
948
954
|
*/
|
|
949
955
|
zoomOutHorizontal() {
|
|
950
956
|
self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
|
|
951
|
-
self.scrollX = clamp(self.
|
|
957
|
+
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
952
958
|
},
|
|
953
959
|
/**
|
|
954
960
|
* #action
|
|
955
961
|
*/
|
|
956
962
|
zoomInHorizontal() {
|
|
957
963
|
self.colWidth = Math.ceil(self.colWidth * 1.5)
|
|
958
|
-
self.scrollX = clamp(self.
|
|
964
|
+
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
959
965
|
},
|
|
960
966
|
/**
|
|
961
967
|
* #action
|
|
@@ -976,7 +982,7 @@ function stateModelFactory() {
|
|
|
976
982
|
transaction(() => {
|
|
977
983
|
self.colWidth = Math.ceil(self.colWidth * 1.5)
|
|
978
984
|
self.rowHeight = Math.ceil(self.rowHeight * 1.5)
|
|
979
|
-
self.scrollX = clamp(self.
|
|
985
|
+
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
980
986
|
})
|
|
981
987
|
},
|
|
982
988
|
/**
|
|
@@ -986,7 +992,7 @@ function stateModelFactory() {
|
|
|
986
992
|
transaction(() => {
|
|
987
993
|
self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
|
|
988
994
|
self.rowHeight = Math.max(1.5, Math.floor(self.rowHeight * 0.75))
|
|
989
|
-
self.scrollX = clamp(self.
|
|
995
|
+
self.scrollX = clamp(self.scrollX, self.maxScrollX, 0)
|
|
990
996
|
})
|
|
991
997
|
},
|
|
992
998
|
/**
|
|
@@ -1000,21 +1006,21 @@ function stateModelFactory() {
|
|
|
1000
1006
|
* #action
|
|
1001
1007
|
*/
|
|
1002
1008
|
doScrollY(deltaY: number) {
|
|
1003
|
-
self.scrollY = clamp(
|
|
1009
|
+
self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
|
|
1004
1010
|
},
|
|
1005
1011
|
|
|
1006
1012
|
/**
|
|
1007
1013
|
* #action
|
|
1008
1014
|
*/
|
|
1009
1015
|
doScrollX(deltaX: number) {
|
|
1010
|
-
self.scrollX = clamp(self.
|
|
1016
|
+
self.scrollX = clamp(self.scrollX + deltaX, self.maxScrollX, 0)
|
|
1011
1017
|
},
|
|
1012
1018
|
|
|
1013
1019
|
/**
|
|
1014
1020
|
* #action
|
|
1015
1021
|
*/
|
|
1016
1022
|
setScrollX(n: number) {
|
|
1017
|
-
self.scrollX = clamp(self.maxScrollX,
|
|
1023
|
+
self.scrollX = clamp(n, self.maxScrollX, 0)
|
|
1018
1024
|
},
|
|
1019
1025
|
|
|
1020
1026
|
/**
|
|
@@ -1137,11 +1143,9 @@ function stateModelFactory() {
|
|
|
1137
1143
|
* #method
|
|
1138
1144
|
* return a row-specific letter, or undefined if gap
|
|
1139
1145
|
*/
|
|
1140
|
-
mouseOverCoordToRowLetter(rowName: string,
|
|
1146
|
+
mouseOverCoordToRowLetter(rowName: string, pos: number) {
|
|
1141
1147
|
const { rowMap, blanks } = self
|
|
1142
|
-
return rowMap.get(rowName)?.[
|
|
1143
|
-
mouseOverCoordToGlobalCoord(blanks, position)
|
|
1144
|
-
]
|
|
1148
|
+
return rowMap.get(rowName)?.[mouseOverCoordToGlobalCoord(blanks, pos)]
|
|
1145
1149
|
},
|
|
1146
1150
|
|
|
1147
1151
|
/**
|
|
@@ -1150,15 +1154,25 @@ function stateModelFactory() {
|
|
|
1150
1154
|
* global coordinate
|
|
1151
1155
|
*/
|
|
1152
1156
|
mouseOverCoordToGapRemovedRowCoord(rowName: string, position: number) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1157
|
+
return mouseOverCoordToGapRemovedRowCoord({
|
|
1158
|
+
rowName,
|
|
1159
|
+
position,
|
|
1160
|
+
rowMap: self.rowMap,
|
|
1161
|
+
blanks: self.blanks,
|
|
1162
|
+
})
|
|
1163
|
+
},
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* #method
|
|
1167
|
+
* return a row-specific sequence coordinate, skipping gaps, given a
|
|
1168
|
+
* global coordinate
|
|
1169
|
+
*/
|
|
1170
|
+
mouseOverCoordToGapRemovedRowCoordOneBased(
|
|
1171
|
+
rowName: string,
|
|
1172
|
+
position: number,
|
|
1173
|
+
) {
|
|
1174
|
+
const val = this.mouseOverCoordToGapRemovedRowCoord(rowName, position)
|
|
1175
|
+
return val !== undefined ? val + 1 : undefined
|
|
1162
1176
|
},
|
|
1163
1177
|
|
|
1164
1178
|
/**
|
|
@@ -1169,21 +1183,12 @@ function stateModelFactory() {
|
|
|
1169
1183
|
seqCoordToRowSpecificGlobalCoord(rowName: string, position: number) {
|
|
1170
1184
|
const { rowNames, rows } = self
|
|
1171
1185
|
const index = rowNames.indexOf(rowName)
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
if (row[i] !== '-') {
|
|
1179
|
-
k++
|
|
1180
|
-
} else if (k >= position) {
|
|
1181
|
-
break
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return i
|
|
1185
|
-
}
|
|
1186
|
-
return 0
|
|
1186
|
+
return index !== -1 && rows[index]
|
|
1187
|
+
? seqCoordToRowSpecificGlobalCoord({
|
|
1188
|
+
row: rows[index][1],
|
|
1189
|
+
position,
|
|
1190
|
+
})
|
|
1191
|
+
: 0
|
|
1187
1192
|
},
|
|
1188
1193
|
}))
|
|
1189
1194
|
|
|
@@ -1321,7 +1326,11 @@ function stateModelFactory() {
|
|
|
1321
1326
|
* #action
|
|
1322
1327
|
*/
|
|
1323
1328
|
reset() {
|
|
1324
|
-
self.setData({
|
|
1329
|
+
self.setData({
|
|
1330
|
+
tree: '',
|
|
1331
|
+
msa: '',
|
|
1332
|
+
})
|
|
1333
|
+
self.resetZoom()
|
|
1325
1334
|
self.setError(undefined)
|
|
1326
1335
|
self.setScrollY(0)
|
|
1327
1336
|
self.setScrollX(0)
|
|
@@ -1402,7 +1411,7 @@ function stateModelFactory() {
|
|
|
1402
1411
|
try {
|
|
1403
1412
|
self.setLoadingTree(true)
|
|
1404
1413
|
self.setTree(
|
|
1405
|
-
await openLocation(treeFilehandle)
|
|
1414
|
+
await fetchAndMaybeUnzipText(openLocation(treeFilehandle)),
|
|
1406
1415
|
)
|
|
1407
1416
|
if (treeFilehandle.locationType === 'BlobLocation') {
|
|
1408
1417
|
// clear filehandle after loading if from a local file
|
|
@@ -1425,7 +1434,9 @@ function stateModelFactory() {
|
|
|
1425
1434
|
if (treeMetadataFilehandle) {
|
|
1426
1435
|
try {
|
|
1427
1436
|
self.setTreeMetadata(
|
|
1428
|
-
await
|
|
1437
|
+
await fetchAndMaybeUnzipText(
|
|
1438
|
+
openLocation(treeMetadataFilehandle),
|
|
1439
|
+
),
|
|
1429
1440
|
)
|
|
1430
1441
|
} catch (e) {
|
|
1431
1442
|
console.error(e)
|
|
@@ -1443,9 +1454,10 @@ function stateModelFactory() {
|
|
|
1443
1454
|
if (msaFilehandle) {
|
|
1444
1455
|
try {
|
|
1445
1456
|
self.setLoadingMSA(true)
|
|
1446
|
-
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1457
|
+
self.setError(undefined)
|
|
1458
|
+
const txt = await fetchAndMaybeUnzipText(
|
|
1459
|
+
openLocation(msaFilehandle),
|
|
1460
|
+
)
|
|
1449
1461
|
transaction(() => {
|
|
1450
1462
|
self.setMSA(txt)
|
|
1451
1463
|
if (msaFilehandle.locationType === 'BlobLocation') {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { expect, test } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
globalCoordToRowSpecificCoord,
|
|
5
|
+
mouseOverCoordToGapRemovedCoord,
|
|
6
|
+
mouseOverCoordToGlobalCoord,
|
|
7
|
+
} from './rowCoordinateCalculations'
|
|
8
|
+
|
|
9
|
+
test('with blanks at positions [2, 5, 8]', () => {
|
|
5
10
|
const blanks = [2, 5, 8]
|
|
6
11
|
;(
|
|
7
12
|
[
|
|
@@ -18,3 +23,134 @@ test('blanks3', () => {
|
|
|
18
23
|
expect(mouseOverCoordToGlobalCoord(blanks, r[0])).toBe(r[1])
|
|
19
24
|
})
|
|
20
25
|
})
|
|
26
|
+
|
|
27
|
+
test('with no blanks', () => {
|
|
28
|
+
const blanks: number[] = []
|
|
29
|
+
;(
|
|
30
|
+
[
|
|
31
|
+
[0, 0],
|
|
32
|
+
[1, 1],
|
|
33
|
+
[5, 5],
|
|
34
|
+
[10, 10],
|
|
35
|
+
] as const
|
|
36
|
+
).forEach(r => {
|
|
37
|
+
expect(mouseOverCoordToGlobalCoord(blanks, r[0])).toBe(r[1])
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('with consecutive blanks', () => {
|
|
42
|
+
const blanks = [2, 3, 4, 7, 8]
|
|
43
|
+
;(
|
|
44
|
+
[
|
|
45
|
+
[0, 0],
|
|
46
|
+
[1, 1],
|
|
47
|
+
[2, 5], // After position 1, skip 3 blanks (2,3,4)
|
|
48
|
+
[3, 6], // Next position
|
|
49
|
+
[4, 9], // After position 3, skip 2 blanks (7,8)
|
|
50
|
+
[5, 10],
|
|
51
|
+
] as const
|
|
52
|
+
).forEach(r => {
|
|
53
|
+
expect(mouseOverCoordToGlobalCoord(blanks, r[0])).toBe(r[1])
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('with blanks at the beginning', () => {
|
|
58
|
+
const blanks = [1, 2, 5]
|
|
59
|
+
;(
|
|
60
|
+
[
|
|
61
|
+
[0, 0],
|
|
62
|
+
[1, 3], // After position 0, skip 2 blanks (1,2)
|
|
63
|
+
[2, 4],
|
|
64
|
+
[3, 6], // After position 2, skip 1 blank (5)
|
|
65
|
+
[4, 7],
|
|
66
|
+
] as const
|
|
67
|
+
).forEach(r => {
|
|
68
|
+
expect(mouseOverCoordToGlobalCoord(blanks, r[0])).toBe(r[1])
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('with position exceeding blanks array', () => {
|
|
73
|
+
const blanks = [2, 5]
|
|
74
|
+
;(
|
|
75
|
+
[
|
|
76
|
+
[0, 0],
|
|
77
|
+
[1, 1],
|
|
78
|
+
[2, 3], // After position 1, skip 1 blank (2)
|
|
79
|
+
[3, 4],
|
|
80
|
+
[4, 6], // After position 3, skip 1 blank (5)
|
|
81
|
+
[10, 12], // Far beyond blanks array
|
|
82
|
+
] as const
|
|
83
|
+
).forEach(r => {
|
|
84
|
+
expect(mouseOverCoordToGlobalCoord(blanks, r[0])).toBe(r[1])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('with gaps in sequence', () => {
|
|
89
|
+
const sequence = 'AC-GT-A'
|
|
90
|
+
expect(globalCoordToRowSpecificCoord(sequence, 0)).toBe(0)
|
|
91
|
+
expect(globalCoordToRowSpecificCoord(sequence, 1)).toBe(1)
|
|
92
|
+
expect(globalCoordToRowSpecificCoord(sequence, 2)).toBe(2)
|
|
93
|
+
// Position 3 in global coordinates is after the gap
|
|
94
|
+
expect(globalCoordToRowSpecificCoord(sequence, 3)).toBe(2)
|
|
95
|
+
expect(globalCoordToRowSpecificCoord(sequence, 4)).toBe(3)
|
|
96
|
+
expect(globalCoordToRowSpecificCoord(sequence, 5)).toBe(4)
|
|
97
|
+
// Position 6 in global coordinates is after the gap
|
|
98
|
+
expect(globalCoordToRowSpecificCoord(sequence, 6)).toBe(4)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('with no gaps in sequence', () => {
|
|
102
|
+
const sequence = 'ACGTA'
|
|
103
|
+
expect(globalCoordToRowSpecificCoord(sequence, 0)).toBe(0)
|
|
104
|
+
expect(globalCoordToRowSpecificCoord(sequence, 1)).toBe(1)
|
|
105
|
+
expect(globalCoordToRowSpecificCoord(sequence, 2)).toBe(2)
|
|
106
|
+
expect(globalCoordToRowSpecificCoord(sequence, 3)).toBe(3)
|
|
107
|
+
expect(globalCoordToRowSpecificCoord(sequence, 4)).toBe(4)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('with all gaps in sequence', () => {
|
|
111
|
+
const sequence = '-----'
|
|
112
|
+
expect(globalCoordToRowSpecificCoord(sequence, 0)).toBe(0)
|
|
113
|
+
expect(globalCoordToRowSpecificCoord(sequence, 1)).toBe(0)
|
|
114
|
+
expect(globalCoordToRowSpecificCoord(sequence, 2)).toBe(0)
|
|
115
|
+
expect(globalCoordToRowSpecificCoord(sequence, 3)).toBe(0)
|
|
116
|
+
expect(globalCoordToRowSpecificCoord(sequence, 4)).toBe(0)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('with position exceeding sequence length', () => {
|
|
120
|
+
const sequence = 'AC-GT'
|
|
121
|
+
expect(globalCoordToRowSpecificCoord(sequence, 10)).toBe(4)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('mouseOverCoordToGapRemovedCoord', () => {
|
|
125
|
+
const seq = 'AC--GT--CT'
|
|
126
|
+
expect(
|
|
127
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 0, blanks: [] }),
|
|
128
|
+
).toBe(0)
|
|
129
|
+
expect(
|
|
130
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 1, blanks: [] }),
|
|
131
|
+
).toBe(1)
|
|
132
|
+
expect(
|
|
133
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 2, blanks: [] }),
|
|
134
|
+
).toBe(undefined)
|
|
135
|
+
expect(
|
|
136
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 3, blanks: [] }),
|
|
137
|
+
).toBe(undefined)
|
|
138
|
+
expect(
|
|
139
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 4, blanks: [] }),
|
|
140
|
+
).toBe(2)
|
|
141
|
+
expect(
|
|
142
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 5, blanks: [] }),
|
|
143
|
+
).toBe(3)
|
|
144
|
+
expect(
|
|
145
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 6, blanks: [] }),
|
|
146
|
+
).toBe(undefined)
|
|
147
|
+
expect(
|
|
148
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 7, blanks: [] }),
|
|
149
|
+
).toBe(undefined)
|
|
150
|
+
expect(
|
|
151
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 8, blanks: [] }),
|
|
152
|
+
).toBe(4)
|
|
153
|
+
expect(
|
|
154
|
+
mouseOverCoordToGapRemovedCoord({ seq, position: 9, blanks: [] }),
|
|
155
|
+
).toBe(5)
|
|
156
|
+
})
|