react-msaview 5.0.13 → 5.1.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/bundle/index.js +135 -35
- package/bundle/index.js.LICENSE.txt +1 -1
- package/bundle/index.js.map +1 -1
- package/dist/clustalX.d.ts +5 -0
- package/dist/clustalX.js +88 -0
- package/dist/clustalX.js.map +1 -0
- package/dist/components/SequenceTextArea.js +6 -6
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +3 -5
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.d.ts +2 -1
- package/dist/components/Track.js +4 -3
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +6 -31
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +5 -3
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +4 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +2 -27
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/SettingsMenu.js +4 -69
- package/dist/components/header/SettingsMenu.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +3 -41
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.d.ts +4 -0
- package/dist/components/header/settingsMenuItems.js +59 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -0
- package/dist/components/import/ImportFormExamples.js +1 -1
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -31
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +31 -33
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +15 -9
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +12 -14
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +9 -3
- package/dist/components/tree/renderTreeCanvas.js +29 -14
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/fetchUtils.d.ts +2 -1
- package/dist/fetchUtils.js +16 -2
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +3 -0
- package/dist/hierarchy.js +25 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/launchInterProScan.d.ts +2 -1
- package/dist/launchInterProScan.js +31 -15
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/model.d.ts +150 -494
- package/dist/model.js +24 -116
- package/dist/model.js.map +1 -1
- package/dist/useColorContrast.d.ts +10 -0
- package/dist/useColorContrast.js +13 -0
- package/dist/useColorContrast.js.map +1 -0
- package/dist/useDragScroll.d.ts +12 -0
- package/dist/useDragScroll.js +47 -0
- package/dist/useDragScroll.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -6
- package/src/clustalX.test.ts +76 -0
- package/src/clustalX.ts +131 -0
- package/src/components/SequenceTextArea.tsx +6 -6
- package/src/components/TextTrack.tsx +3 -8
- package/src/components/Track.tsx +6 -9
- package/src/components/VerticalScrollbar.tsx +13 -40
- package/src/components/dialogs/InterProScanDialog.tsx +7 -1
- package/src/components/header/HeaderStatusArea.tsx +6 -1
- package/src/components/header/MSASettingsMenu.tsx +3 -27
- package/src/components/header/SettingsMenu.tsx +7 -79
- package/src/components/header/TreeSettingsMenu.tsx +4 -48
- package/src/components/header/settingsMenuItems.ts +68 -0
- package/src/components/import/ImportFormExamples.tsx +1 -1
- package/src/components/minimap/Minimap.tsx +13 -38
- package/src/components/msa/MSACanvasBlock.tsx +53 -36
- package/src/components/msa/renderMSABlock.ts +16 -9
- package/src/components/tree/TreeCanvas.tsx +13 -35
- package/src/components/tree/renderTreeCanvas.test.ts +205 -0
- package/src/components/tree/renderTreeCanvas.ts +46 -11
- package/src/fetchUtils.ts +16 -2
- package/src/hierarchy.ts +28 -0
- package/src/launchInterProScan.ts +34 -10
- package/src/model.ts +33 -143
- package/src/useColorContrast.ts +18 -0
- package/src/useDragScroll.ts +55 -0
- package/src/version.ts +1 -1
package/src/fetchUtils.ts
CHANGED
|
@@ -20,6 +20,20 @@ export async function jsonfetch<T>(url: string, args?: RequestInit) {
|
|
|
20
20
|
return response.json() as T
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function timeout(time: number) {
|
|
24
|
-
return new Promise(
|
|
23
|
+
export function timeout(time: number, signal?: AbortSignal) {
|
|
24
|
+
return new Promise<void>((resolve, reject) => {
|
|
25
|
+
if (signal?.aborted) {
|
|
26
|
+
reject(new DOMException('Aborted', 'AbortError'))
|
|
27
|
+
} else {
|
|
28
|
+
const id = setTimeout(resolve, time)
|
|
29
|
+
signal?.addEventListener('abort', () => {
|
|
30
|
+
clearTimeout(id)
|
|
31
|
+
reject(new DOMException('Aborted', 'AbortError'))
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isAbortError(e: unknown) {
|
|
38
|
+
return e instanceof DOMException && e.name === 'AbortError'
|
|
25
39
|
}
|
package/src/hierarchy.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface HierarchyNode<T = NodeWithIds> {
|
|
|
10
10
|
x?: number
|
|
11
11
|
y?: number
|
|
12
12
|
len?: number
|
|
13
|
+
depthToLeaf?: number
|
|
13
14
|
_children?: HierarchyNode<T>[] | null
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -203,6 +204,33 @@ export function collapse<T>(node: HierarchyNode<T>) {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
// Cladogram positioning based on ape's plot.phylo: uses topological depth (max
|
|
208
|
+
// steps to a tip) instead of branch length so all leaves align at the rightmost
|
|
209
|
+
// x. Memoizes onto node.depthToLeaf since the layout walks the tree repeatedly.
|
|
210
|
+
// See https://github.com/emmanuelparadis/ape/blob/master/R/plot.phylo.R
|
|
211
|
+
export function calcDepthToLeaf(node: HierarchyNode): number {
|
|
212
|
+
if (node.depthToLeaf === undefined) {
|
|
213
|
+
let maxDepth = 0
|
|
214
|
+
if (node.children) {
|
|
215
|
+
for (const child of node.children) {
|
|
216
|
+
maxDepth = Math.max(maxDepth, 1 + calcDepthToLeaf(child))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
node.depthToLeaf = maxDepth
|
|
220
|
+
}
|
|
221
|
+
return node.depthToLeaf
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function findMaxBranchLen(node: HierarchyNode): number {
|
|
225
|
+
let maxLen = node.len || 0
|
|
226
|
+
if (node.children) {
|
|
227
|
+
for (const child of node.children) {
|
|
228
|
+
maxLen = Math.max(maxLen, findMaxBranchLen(child))
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return maxLen
|
|
232
|
+
}
|
|
233
|
+
|
|
206
234
|
export function maxLength(d: HierarchyNode): number {
|
|
207
235
|
return (
|
|
208
236
|
(d.data.length || 0) +
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util'
|
|
2
2
|
|
|
3
|
-
import { jsonfetch, textfetch, timeout } from './fetchUtils.ts'
|
|
3
|
+
import { isAbortError, jsonfetch, textfetch, timeout } from './fetchUtils.ts'
|
|
4
4
|
|
|
5
5
|
import type { MsaViewModel } from './model.ts'
|
|
6
6
|
|
|
@@ -29,12 +29,14 @@ async function runInterProScan({
|
|
|
29
29
|
onJobId,
|
|
30
30
|
programs,
|
|
31
31
|
model,
|
|
32
|
+
signal,
|
|
32
33
|
}: {
|
|
33
34
|
seq: string
|
|
34
35
|
programs: string[]
|
|
35
36
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
36
37
|
onJobId?: (arg: string) => void
|
|
37
38
|
model: MsaViewModel
|
|
39
|
+
signal?: AbortSignal
|
|
38
40
|
}) {
|
|
39
41
|
const jobId = await textfetch(`${base}/iprscan5/run`, {
|
|
40
42
|
method: 'POST',
|
|
@@ -43,37 +45,42 @@ async function runInterProScan({
|
|
|
43
45
|
sequence: seq,
|
|
44
46
|
programs: programs.join(','),
|
|
45
47
|
}),
|
|
48
|
+
signal,
|
|
46
49
|
})
|
|
47
50
|
onJobId?.(jobId)
|
|
48
51
|
await wait({
|
|
49
52
|
jobId,
|
|
50
53
|
onProgress,
|
|
54
|
+
signal,
|
|
51
55
|
})
|
|
52
|
-
await loadInterProScanResultsWithStatus({ jobId, model })
|
|
56
|
+
await loadInterProScanResultsWithStatus({ jobId, model, onProgress, signal })
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
function loadInterProScanResults(jobId: string) {
|
|
59
|
+
function loadInterProScanResults(jobId: string, signal?: AbortSignal) {
|
|
56
60
|
return jsonfetch<InterProScanResponse>(
|
|
57
61
|
`${base}/iprscan5/result/${jobId}/json`,
|
|
62
|
+
{ signal },
|
|
58
63
|
)
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
async function wait({
|
|
62
67
|
onProgress,
|
|
63
68
|
jobId,
|
|
69
|
+
signal,
|
|
64
70
|
}: {
|
|
65
71
|
jobId: string
|
|
66
72
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
73
|
+
signal?: AbortSignal
|
|
67
74
|
}) {
|
|
68
75
|
const url = `${base}/iprscan5/status/${jobId}`
|
|
69
76
|
try {
|
|
70
77
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
71
78
|
while (true) {
|
|
72
79
|
for (let i = 0; i < 10; i++) {
|
|
73
|
-
await timeout(1000)
|
|
80
|
+
await timeout(1000, signal)
|
|
74
81
|
onProgress({ msg: `Checking status ${10 - i}`, url })
|
|
75
82
|
}
|
|
76
|
-
const result = await textfetch(url)
|
|
83
|
+
const result = await textfetch(url, { signal })
|
|
77
84
|
if (result.includes('FINISHED')) {
|
|
78
85
|
break
|
|
79
86
|
}
|
|
@@ -93,6 +100,7 @@ export async function launchInterProScan({
|
|
|
93
100
|
onJobId,
|
|
94
101
|
onProgress,
|
|
95
102
|
model,
|
|
103
|
+
signal,
|
|
96
104
|
}: {
|
|
97
105
|
algorithm: string
|
|
98
106
|
seq: string
|
|
@@ -100,6 +108,7 @@ export async function launchInterProScan({
|
|
|
100
108
|
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
101
109
|
onJobId?: (arg: string) => void
|
|
102
110
|
model: MsaViewModel
|
|
111
|
+
signal?: AbortSignal
|
|
103
112
|
}) {
|
|
104
113
|
try {
|
|
105
114
|
onProgress({ msg: `Launching ${algorithm} MSA` })
|
|
@@ -110,10 +119,17 @@ export async function launchInterProScan({
|
|
|
110
119
|
onProgress,
|
|
111
120
|
programs,
|
|
112
121
|
model,
|
|
122
|
+
signal,
|
|
113
123
|
})
|
|
114
124
|
} else {
|
|
115
125
|
throw new Error('unknown algorithm')
|
|
116
126
|
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (isAbortError(e)) {
|
|
129
|
+
getSession(model).notify('Cancelled InterProScan query', 'info')
|
|
130
|
+
} else {
|
|
131
|
+
throw e
|
|
132
|
+
}
|
|
117
133
|
} finally {
|
|
118
134
|
onProgress()
|
|
119
135
|
}
|
|
@@ -122,25 +138,33 @@ export async function launchInterProScan({
|
|
|
122
138
|
async function loadInterProScanResultsWithStatus({
|
|
123
139
|
jobId,
|
|
124
140
|
model,
|
|
141
|
+
onProgress,
|
|
142
|
+
signal,
|
|
125
143
|
}: {
|
|
126
144
|
jobId: string
|
|
127
145
|
model: MsaViewModel
|
|
146
|
+
onProgress: (arg?: { msg: string; url?: string }) => void
|
|
147
|
+
signal?: AbortSignal
|
|
128
148
|
}) {
|
|
129
149
|
try {
|
|
130
|
-
|
|
150
|
+
onProgress({
|
|
131
151
|
msg: `Downloading results of ${jobId} (for larger sequences this can be slow, click status to download and upload in the manual tab)`,
|
|
132
152
|
url: `https://www.ebi.ac.uk/Tools/services/rest/iprscan5/result/${jobId}/json`,
|
|
133
153
|
})
|
|
134
|
-
const ret = await loadInterProScanResults(jobId)
|
|
154
|
+
const ret = await loadInterProScanResults(jobId, signal)
|
|
135
155
|
model.setInterProAnnotations(
|
|
136
156
|
Object.fromEntries(ret.results.map(r => [r.xref[0]!.id, r])),
|
|
137
157
|
)
|
|
138
158
|
model.setShowDomains(true)
|
|
139
159
|
getSession(model).notify(`Loaded interproscan ${jobId} results`, 'success')
|
|
140
160
|
} catch (e) {
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
if (isAbortError(e)) {
|
|
162
|
+
getSession(model).notify('Cancelled InterProScan query', 'info')
|
|
163
|
+
} else {
|
|
164
|
+
console.error(e)
|
|
165
|
+
getSession(model).notifyError(`${e}`, e)
|
|
166
|
+
}
|
|
143
167
|
} finally {
|
|
144
|
-
|
|
168
|
+
onProgress()
|
|
145
169
|
}
|
|
146
170
|
}
|
package/src/model.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from 'msa-parsers'
|
|
21
21
|
|
|
22
22
|
import { blocksX, blocksY } from './calculateBlocks.ts'
|
|
23
|
+
import { clustalXColumnColors } from './clustalX.ts'
|
|
23
24
|
import colorSchemes from './colorSchemes.ts'
|
|
24
25
|
import ConservationTrack from './components/ConservationTrack.tsx'
|
|
25
26
|
import TextTrack from './components/TextTrack.tsx'
|
|
@@ -48,9 +49,11 @@ import {
|
|
|
48
49
|
import { createPaletteMap } from './createPaletteMap.ts'
|
|
49
50
|
import { flatToTree } from './flatToTree.ts'
|
|
50
51
|
import {
|
|
52
|
+
calcDepthToLeaf,
|
|
51
53
|
clusterLayout,
|
|
52
54
|
collapse,
|
|
53
55
|
find,
|
|
56
|
+
findMaxBranchLen,
|
|
54
57
|
hierarchy,
|
|
55
58
|
leaves,
|
|
56
59
|
links,
|
|
@@ -90,9 +93,9 @@ import type { Theme } from '@mui/material'
|
|
|
90
93
|
/**
|
|
91
94
|
* #stateModel MsaView
|
|
92
95
|
* extends
|
|
93
|
-
* - DialogQueueSessionMixin
|
|
94
|
-
* - MSAModel
|
|
95
|
-
* - Tree
|
|
96
|
+
* - [DialogQueueSessionMixin](../dialogqueuesessionmixin)
|
|
97
|
+
* - [MSAModel](../msamodel)
|
|
98
|
+
* - [Tree](../tree)
|
|
96
99
|
*/
|
|
97
100
|
function stateModelFactory() {
|
|
98
101
|
return types
|
|
@@ -242,7 +245,9 @@ function stateModelFactory() {
|
|
|
242
245
|
/**
|
|
243
246
|
* #volatile
|
|
244
247
|
*/
|
|
245
|
-
status: undefined as
|
|
248
|
+
status: undefined as
|
|
249
|
+
| { msg: string; url?: string; onCancel?: () => void }
|
|
250
|
+
| undefined,
|
|
246
251
|
/**
|
|
247
252
|
* #volatile
|
|
248
253
|
* high resolution scale factor, helps make canvas look better on hi-dpi
|
|
@@ -337,6 +342,7 @@ function stateModelFactory() {
|
|
|
337
342
|
/**
|
|
338
343
|
* #volatile
|
|
339
344
|
*/
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
340
346
|
error: undefined as unknown,
|
|
341
347
|
|
|
342
348
|
/**
|
|
@@ -921,13 +927,9 @@ function stateModelFactory() {
|
|
|
921
927
|
const columns = this.columns2d
|
|
922
928
|
for (const column of columns) {
|
|
923
929
|
for (let j = 0; j < column.length; j++) {
|
|
924
|
-
const l = r[j]
|
|
930
|
+
const l = (r[j] ??= {})
|
|
925
931
|
const cj = column[j]!
|
|
926
|
-
|
|
927
|
-
l[cj] = 0
|
|
928
|
-
}
|
|
929
|
-
l[cj]++
|
|
930
|
-
r[j] = l
|
|
932
|
+
l[cj] = (l[cj] ?? 0) + 1
|
|
931
933
|
}
|
|
932
934
|
}
|
|
933
935
|
return r
|
|
@@ -1005,136 +1007,9 @@ function stateModelFactory() {
|
|
|
1005
1007
|
*/
|
|
1006
1008
|
get colClustalX() {
|
|
1007
1009
|
const { colStats, colStatsSums } = this
|
|
1008
|
-
return colStats.map((stats, i) =>
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const W = stats.W ?? 0
|
|
1013
|
-
const L = stats.L ?? 0
|
|
1014
|
-
const V = stats.V ?? 0
|
|
1015
|
-
const I = stats.I ?? 0
|
|
1016
|
-
const M = stats.M ?? 0
|
|
1017
|
-
const A = stats.A ?? 0
|
|
1018
|
-
const F = stats.F ?? 0
|
|
1019
|
-
const C = stats.C ?? 0
|
|
1020
|
-
const H = stats.H ?? 0
|
|
1021
|
-
const P = stats.P ?? 0
|
|
1022
|
-
const R = stats.R ?? 0
|
|
1023
|
-
const K = stats.K ?? 0
|
|
1024
|
-
const Q = stats.Q ?? 0
|
|
1025
|
-
const E = stats.E ?? 0
|
|
1026
|
-
const D = stats.D ?? 0
|
|
1027
|
-
const T = stats.T ?? 0
|
|
1028
|
-
const S = stats.S ?? 0
|
|
1029
|
-
const G = stats.G ?? 0
|
|
1030
|
-
const Y = stats.Y ?? 0
|
|
1031
|
-
const N = stats.N ?? 0
|
|
1032
|
-
|
|
1033
|
-
const WLVIMAFCHPY = W + L + V + I + M + A + F + C + H + P + Y
|
|
1034
|
-
const KR = K + R
|
|
1035
|
-
const QE = Q + E
|
|
1036
|
-
const ED = E + D
|
|
1037
|
-
const TS = T + S
|
|
1038
|
-
|
|
1039
|
-
if (WLVIMAFCHPY / total > 0.6) {
|
|
1040
|
-
colors.W = 'rgb(128,179,230)'
|
|
1041
|
-
colors.L = 'rgb(128,179,230)'
|
|
1042
|
-
colors.V = 'rgb(128,179,230)'
|
|
1043
|
-
colors.A = 'rgb(128,179,230)'
|
|
1044
|
-
colors.I = 'rgb(128,179,230)'
|
|
1045
|
-
colors.M = 'rgb(128,179,230)'
|
|
1046
|
-
colors.F = 'rgb(128,179,230)'
|
|
1047
|
-
colors.C = 'rgb(128,179,230)'
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
if (
|
|
1051
|
-
KR / total > 0.6 ||
|
|
1052
|
-
K / total > 0.8 ||
|
|
1053
|
-
R / total > 0.8 ||
|
|
1054
|
-
Q / total > 0.8
|
|
1055
|
-
) {
|
|
1056
|
-
colors.K = '#d88'
|
|
1057
|
-
colors.R = '#d88'
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
if (
|
|
1061
|
-
KR / total > 0.6 ||
|
|
1062
|
-
QE / total > 0.5 ||
|
|
1063
|
-
E / total > 0.8 ||
|
|
1064
|
-
Q / total > 0.8 ||
|
|
1065
|
-
D / total > 0.8
|
|
1066
|
-
) {
|
|
1067
|
-
colors.E = 'rgb(192, 72, 192)'
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
if (
|
|
1071
|
-
KR / total > 0.6 ||
|
|
1072
|
-
ED / total > 0.5 ||
|
|
1073
|
-
K / total > 0.8 ||
|
|
1074
|
-
R / total > 0.8 ||
|
|
1075
|
-
Q / total > 0.8
|
|
1076
|
-
) {
|
|
1077
|
-
colors.D = 'rgb(204, 77, 204)'
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
if (N / total > 0.5 || Y / total > 0.85) {
|
|
1081
|
-
colors.N = '#8f8'
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
if (
|
|
1085
|
-
KR / total > 0.6 ||
|
|
1086
|
-
QE / total > 0.6 ||
|
|
1087
|
-
Q / total > 0.85 ||
|
|
1088
|
-
E / total > 0.85 ||
|
|
1089
|
-
K / total > 0.85 ||
|
|
1090
|
-
R / total > 0.85
|
|
1091
|
-
) {
|
|
1092
|
-
colors.Q = '#8f8'
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1096
|
-
WLVIMAFCHPY / total > 0.6 ||
|
|
1097
|
-
TS / total > 0.5 ||
|
|
1098
|
-
S / total > 0.85 ||
|
|
1099
|
-
T / total > 0.85
|
|
1100
|
-
) {
|
|
1101
|
-
colors.S = 'rgb(26,204,26)'
|
|
1102
|
-
colors.T = 'rgb(26,204,26)'
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
if (C / total > 0.85) {
|
|
1106
|
-
colors.C = 'rgb(240, 128, 128)'
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if (G / total > 0) {
|
|
1110
|
-
colors.G = 'rgb(240, 144, 72)'
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
if (P / total > 0) {
|
|
1114
|
-
colors.P = 'rgb(204, 204, 0)'
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if (
|
|
1118
|
-
WLVIMAFCHPY / total > 0.6 ||
|
|
1119
|
-
W / total > 0.85 ||
|
|
1120
|
-
Y / total > 0.85 ||
|
|
1121
|
-
A / total > 0.85 ||
|
|
1122
|
-
C / total > 0.85 ||
|
|
1123
|
-
P / total > 0.85 ||
|
|
1124
|
-
Q / total > 0.85 ||
|
|
1125
|
-
F / total > 0.85 ||
|
|
1126
|
-
H / total > 0.85 ||
|
|
1127
|
-
I / total > 0.85 ||
|
|
1128
|
-
L / total > 0.85 ||
|
|
1129
|
-
M / total > 0.85 ||
|
|
1130
|
-
V / total > 0.85
|
|
1131
|
-
) {
|
|
1132
|
-
colors.H = 'rgb(26, 179, 179)'
|
|
1133
|
-
colors.Y = 'rgb(26, 179, 179)'
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
return colors
|
|
1137
|
-
})
|
|
1010
|
+
return colStats.map((stats, i) =>
|
|
1011
|
+
clustalXColumnColors(stats, colStatsSums[i]!),
|
|
1012
|
+
)
|
|
1138
1013
|
},
|
|
1139
1014
|
|
|
1140
1015
|
/**
|
|
@@ -1208,6 +1083,22 @@ function stateModelFactory() {
|
|
|
1208
1083
|
return leaves(this.hierarchy)
|
|
1209
1084
|
},
|
|
1210
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* #getter
|
|
1088
|
+
* max branch length across the tree, used to scale phylogram x-positions
|
|
1089
|
+
*/
|
|
1090
|
+
get maxBranchLength() {
|
|
1091
|
+
return findMaxBranchLen(this.hierarchy)
|
|
1092
|
+
},
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* #getter
|
|
1096
|
+
* max topological depth to a tip, used to scale cladogram x-positions
|
|
1097
|
+
*/
|
|
1098
|
+
get maxDepthToLeaf() {
|
|
1099
|
+
return calcDepthToLeaf(this.hierarchy)
|
|
1100
|
+
},
|
|
1101
|
+
|
|
1211
1102
|
/**
|
|
1212
1103
|
* #getter
|
|
1213
1104
|
*/
|
|
@@ -1419,7 +1310,7 @@ function stateModelFactory() {
|
|
|
1419
1310
|
/**
|
|
1420
1311
|
* #action
|
|
1421
1312
|
*/
|
|
1422
|
-
setStatus(status?: { msg: string; url?: string }) {
|
|
1313
|
+
setStatus(status?: { msg: string; url?: string; onCancel?: () => void }) {
|
|
1423
1314
|
self.status = status
|
|
1424
1315
|
},
|
|
1425
1316
|
}))
|
|
@@ -1785,8 +1676,7 @@ function stateModelFactory() {
|
|
|
1785
1676
|
},
|
|
1786
1677
|
|
|
1787
1678
|
initFilter(arg: string) {
|
|
1788
|
-
|
|
1789
|
-
if (ret === undefined) {
|
|
1679
|
+
if (!self.featureFilters.has(arg)) {
|
|
1790
1680
|
self.featureFilters.set(arg, true)
|
|
1791
1681
|
}
|
|
1792
1682
|
},
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
import { colorContrast } from './util.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the current theme and a memoized contrast-color scheme for a given
|
|
9
|
+
* color scheme. Shared by the MSA and text-track canvas blocks.
|
|
10
|
+
*/
|
|
11
|
+
export function useColorContrast(colorScheme: Record<string, string>) {
|
|
12
|
+
const theme = useTheme()
|
|
13
|
+
const contrastScheme = useMemo(
|
|
14
|
+
() => colorContrast(colorScheme, theme),
|
|
15
|
+
[colorScheme, theme],
|
|
16
|
+
)
|
|
17
|
+
return { theme, contrastScheme }
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks a mouse drag along a single axis and reports the scroll delta on each
|
|
5
|
+
* animation frame. Shared by the horizontal Minimap and the VerticalScrollbar.
|
|
6
|
+
*
|
|
7
|
+
* `onScroll` is invoked with the pixel delta since drag start and the scroll
|
|
8
|
+
* offset captured when the drag began. Memoize it (useCallback) so the document
|
|
9
|
+
* listeners are not re-attached on every render.
|
|
10
|
+
*/
|
|
11
|
+
export function useDragScroll(
|
|
12
|
+
axis: 'x' | 'y',
|
|
13
|
+
onScroll: (delta: number, startScroll: number) => void,
|
|
14
|
+
) {
|
|
15
|
+
const scheduled = useRef(false)
|
|
16
|
+
const [mouseDown, setMouseDown] = useState<{
|
|
17
|
+
client: number
|
|
18
|
+
startScroll: number
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (mouseDown !== undefined) {
|
|
23
|
+
const { client: startClient, startScroll } = mouseDown
|
|
24
|
+
function onMove(event: MouseEvent) {
|
|
25
|
+
if (!scheduled.current) {
|
|
26
|
+
scheduled.current = true
|
|
27
|
+
requestAnimationFrame(() => {
|
|
28
|
+
const client = axis === 'x' ? event.clientX : event.clientY
|
|
29
|
+
onScroll(client - startClient, startScroll)
|
|
30
|
+
scheduled.current = false
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function onUp() {
|
|
35
|
+
setMouseDown(undefined)
|
|
36
|
+
}
|
|
37
|
+
document.addEventListener('mousemove', onMove)
|
|
38
|
+
document.addEventListener('mouseup', onUp)
|
|
39
|
+
return () => {
|
|
40
|
+
document.removeEventListener('mousemove', onMove)
|
|
41
|
+
document.removeEventListener('mouseup', onUp)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, [axis, onScroll, mouseDown])
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
dragging: mouseDown !== undefined,
|
|
48
|
+
startDrag: (event: React.MouseEvent, startScroll: number) => {
|
|
49
|
+
setMouseDown({
|
|
50
|
+
client: axis === 'x' ? event.clientX : event.clientY,
|
|
51
|
+
startScroll,
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.0
|
|
1
|
+
export const version = '5.1.0'
|