react-msaview 2.0.0 → 2.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 +35 -33
- package/dist/UniprotTrack.js +6 -5
- package/dist/UniprotTrack.js.map +1 -1
- package/dist/colorSchemes.d.ts +3 -9
- package/dist/colorSchemes.js +4 -4
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/BoxTrack.d.ts +4 -3
- package/dist/components/BoxTrack.js +6 -137
- package/dist/components/BoxTrack.js.map +1 -1
- package/dist/components/BoxTrackBlock.d.ts +8 -0
- package/dist/components/BoxTrackBlock.js +136 -0
- package/dist/components/BoxTrackBlock.js.map +1 -0
- package/dist/components/Header.d.ts +2 -1
- package/dist/components/Header.js +29 -27
- package/dist/components/Header.js.map +1 -1
- package/dist/components/ImportForm.d.ts +2 -1
- package/dist/components/MSABlock.d.ts +8 -0
- package/dist/components/MSABlock.js +103 -0
- package/dist/components/MSABlock.js.map +1 -0
- package/dist/components/MSACanvas.d.ts +2 -1
- package/dist/components/MSACanvas.js +3 -101
- package/dist/components/MSACanvas.js.map +1 -1
- package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
- package/dist/components/MSAMouseoverCanvas.js +52 -0
- package/dist/components/MSAMouseoverCanvas.js.map +1 -0
- package/dist/components/MSAView.d.ts +2 -1
- package/dist/components/MSAView.js +10 -36
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MultiAlignmentSelector.d.ts +6 -0
- package/dist/components/MultiAlignmentSelector.js +13 -0
- package/dist/components/MultiAlignmentSelector.js.map +1 -0
- package/dist/components/ResizeHandles.d.ts +3 -2
- package/dist/components/Rubberband.d.ts +1 -1
- package/dist/components/Rubberband.js +0 -1
- package/dist/components/Rubberband.js.map +1 -1
- package/dist/components/Ruler.d.ts +2 -1
- package/dist/components/Ruler.js +2 -2
- package/dist/components/Ruler.js.map +1 -1
- package/dist/components/TextTrack.d.ts +2 -1
- package/dist/components/Track.d.ts +3 -2
- package/dist/components/Track.js +4 -3
- package/dist/components/Track.js.map +1 -1
- package/dist/components/TreeBranchMenu.d.ts +14 -0
- package/dist/components/TreeBranchMenu.js +26 -0
- package/dist/components/TreeBranchMenu.js.map +1 -0
- package/dist/components/TreeCanvas.d.ts +2 -1
- package/dist/components/TreeCanvas.js +2 -320
- package/dist/components/TreeCanvas.js.map +1 -1
- package/dist/components/TreeCanvasBlock.d.ts +7 -0
- package/dist/components/TreeCanvasBlock.js +252 -0
- package/dist/components/TreeCanvasBlock.js.map +1 -0
- package/dist/components/TreeMenu.d.ts +12 -0
- package/dist/components/TreeMenu.js +56 -0
- package/dist/components/TreeMenu.js.map +1 -0
- package/dist/components/TreeRuler.d.ts +2 -1
- package/dist/components/VerticalGuide.d.ts +2 -1
- package/dist/components/ZoomControls.d.ts +6 -0
- package/dist/components/ZoomControls.js +58 -0
- package/dist/components/ZoomControls.js.map +1 -0
- package/dist/components/dialogs/AboutDlg.d.ts +4 -0
- package/dist/components/{AboutDlg.js → dialogs/AboutDlg.js} +3 -3
- package/dist/components/dialogs/AboutDlg.js.map +1 -0
- package/dist/components/{AddTrackDlg.d.ts → dialogs/AddTrackDlg.d.ts} +3 -2
- package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
- package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -2
- package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
- package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
- package/dist/components/{DetailsDlg.js → dialogs/DetailsDlg.js} +3 -2
- package/dist/components/dialogs/DetailsDlg.js.map +1 -0
- package/dist/components/{MoreInfoDlg.d.ts → dialogs/MoreInfoDlg.d.ts} +2 -1
- package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
- package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
- package/dist/components/{SettingsDlg.js → dialogs/SettingsDlg.js} +4 -3
- package/dist/components/dialogs/SettingsDlg.js.map +1 -0
- package/dist/components/{TrackInfoDlg.d.ts → dialogs/TrackInfoDlg.d.ts} +2 -1
- package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
- package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
- package/dist/components/{TracklistDlg.js → dialogs/TracklistDlg.js} +2 -2
- package/dist/components/dialogs/TracklistDlg.js.map +1 -0
- package/dist/components/util.js +0 -1
- package/dist/components/util.js.map +1 -1
- package/dist/model.d.ts +36 -39
- package/dist/model.js +25 -22
- package/dist/model.js.map +1 -1
- package/dist/parseNewick.js +2 -2
- package/dist/parseNewick.js.map +1 -1
- package/dist/parsers/FastaMSA.d.ts +1 -3
- package/dist/parsers/FastaMSA.js +2 -2
- package/dist/parsers/FastaMSA.js.map +1 -1
- package/dist/parsers/StockholmMSA.d.ts +3 -5
- package/dist/parsers/StockholmMSA.js +3 -3
- package/dist/parsers/StockholmMSA.js.map +1 -1
- package/dist/util.d.ts +5 -7
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +9 -9
- package/src/UniprotTrack.ts +6 -7
- package/src/colorSchemes.ts +7 -7
- package/src/components/BoxTrack.tsx +6 -198
- package/src/components/BoxTrackBlock.tsx +198 -0
- package/src/components/Header.tsx +49 -60
- package/src/components/MSABlock.tsx +164 -0
- package/src/components/MSACanvas.tsx +3 -158
- package/src/components/MSAMouseoverCanvas.tsx +87 -0
- package/src/components/MSAView.tsx +19 -63
- package/src/components/MultiAlignmentSelector.tsx +33 -0
- package/src/components/Rubberband.tsx +0 -1
- package/src/components/Ruler.tsx +2 -1
- package/src/components/Track.tsx +9 -6
- package/src/components/TreeBranchMenu.tsx +67 -0
- package/src/components/TreeCanvas.tsx +2 -507
- package/src/components/TreeCanvasBlock.tsx +359 -0
- package/src/components/TreeMenu.tsx +105 -0
- package/src/components/ZoomControls.tsx +78 -0
- package/src/components/{AboutDlg.tsx → dialogs/AboutDlg.tsx} +3 -9
- package/src/components/{AddTrackDlg.tsx → dialogs/AddTrackDlg.tsx} +1 -1
- package/src/components/{AnnotationDlg.tsx → dialogs/AnnotationDlg.tsx} +2 -2
- package/src/components/{DetailsDlg.tsx → dialogs/DetailsDlg.tsx} +5 -5
- package/src/components/{SettingsDlg.tsx → dialogs/SettingsDlg.tsx} +6 -6
- package/src/components/{TracklistDlg.tsx → dialogs/TracklistDlg.tsx} +2 -4
- package/src/components/util.ts +0 -1
- package/src/declare.d.ts +0 -1
- package/src/model.ts +32 -29
- package/src/parseNewick.ts +2 -3
- package/src/parsers/FastaMSA.ts +3 -3
- package/src/parsers/StockholmMSA.ts +5 -5
- package/src/util.ts +3 -3
- package/src/version.ts +1 -1
- package/dist/components/AboutDlg.d.ts +0 -4
- package/dist/components/AboutDlg.js.map +0 -1
- package/dist/components/AddTrackDlg.js.map +0 -1
- package/dist/components/AnnotationDlg.js.map +0 -1
- package/dist/components/DetailsDlg.d.ts +0 -7
- package/dist/components/DetailsDlg.js.map +0 -1
- package/dist/components/MoreInfoDlg.js.map +0 -1
- package/dist/components/SettingsDlg.d.ts +0 -7
- package/dist/components/SettingsDlg.js.map +0 -1
- package/dist/components/TrackInfoDlg.js.map +0 -1
- package/dist/components/TracklistDlg.d.ts +0 -7
- package/dist/components/TracklistDlg.js.map +0 -1
- /package/dist/components/{AddTrackDlg.js → dialogs/AddTrackDlg.js} +0 -0
- /package/dist/components/{AnnotationDlg.js → dialogs/AnnotationDlg.js} +0 -0
- /package/dist/components/{MoreInfoDlg.js → dialogs/MoreInfoDlg.js} +0 -0
- /package/dist/components/{TrackInfoDlg.js → dialogs/TrackInfoDlg.js} +0 -0
- /package/src/components/{MoreInfoDlg.tsx → dialogs/MoreInfoDlg.tsx} +0 -0
- /package/src/components/{TrackInfoDlg.tsx → dialogs/TrackInfoDlg.tsx} +0 -0
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"author": "Colin",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.1.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"files": [
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"node": ">=10"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"lint": "eslint src",
|
|
18
17
|
"clean": "rimraf dist",
|
|
19
18
|
"prebuild": "npm run clean",
|
|
20
|
-
"prepublishOnly": "node -
|
|
21
|
-
"build": "tsc
|
|
22
|
-
"
|
|
23
|
-
"
|
|
19
|
+
"prepublishOnly": "node output-version.js > src/version.ts && git add -A src && git commit -m '[skip ci] Bump version.ts'",
|
|
20
|
+
"build:esm": "tsc",
|
|
21
|
+
"build:bundle": "rollup -c",
|
|
22
|
+
"build": "npm run build:esm && npm run build:bundle",
|
|
23
|
+
"prepack": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@jbrowse/core": ">=2.0.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"react-dom": ">=16.8.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@rollup/plugin-commonjs": "^
|
|
36
|
+
"@rollup/plugin-commonjs": "^25.0.4",
|
|
37
37
|
"@rollup/plugin-json": "^6.0.0",
|
|
38
38
|
"@rollup/plugin-node-resolve": "^15.0.2",
|
|
39
39
|
"@rollup/plugin-replace": "^5.0.2",
|
|
@@ -43,14 +43,14 @@
|
|
|
43
43
|
"@types/d3": "^7.4.0",
|
|
44
44
|
"@types/react": "^18.2.0",
|
|
45
45
|
"@types/react-dom": "^18.2.1",
|
|
46
|
-
"rollup": "^3.
|
|
46
|
+
"rollup": "^3.0.2",
|
|
47
47
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
|
48
48
|
"tslib": "^2.1.0",
|
|
49
49
|
"typescript": "^5.0.4"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"clustal-js": "^1.0.3",
|
|
53
|
-
"color": "^
|
|
53
|
+
"color": "^4.2.3",
|
|
54
54
|
"copy-to-clipboard": "^3.3.1",
|
|
55
55
|
"d3-array": "^3.2.3",
|
|
56
56
|
"d3-hierarchy": "^3.1.2",
|
package/src/UniprotTrack.ts
CHANGED
|
@@ -31,16 +31,15 @@ export const UniprotTrack = types
|
|
|
31
31
|
autorun(async () => {
|
|
32
32
|
try {
|
|
33
33
|
const { accession } = self
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
34
|
+
const accessionSansVersion = accession?.replace(/\.[^/.]+$/, '')
|
|
35
|
+
const url = `https://rest.uniprot.org/uniprotkb/${accessionSansVersion}.gff`
|
|
36
|
+
const res = await fetch(url)
|
|
37
|
+
if (!res.ok) {
|
|
37
38
|
throw new Error(
|
|
38
|
-
`HTTP ${
|
|
39
|
-
response.status
|
|
40
|
-
} fetching ${url}: ${await response.text()}`,
|
|
39
|
+
`HTTP ${res.status} fetching ${url}: ${await res.text()}`,
|
|
41
40
|
)
|
|
42
41
|
}
|
|
43
|
-
self.setData(await
|
|
42
|
+
self.setData(await res.text())
|
|
44
43
|
} catch (e) {
|
|
45
44
|
console.error(e)
|
|
46
45
|
self.setError(e)
|
package/src/colorSchemes.ts
CHANGED
|
@@ -332,7 +332,7 @@ const colorSchemes = {
|
|
|
332
332
|
'-': 'gray',
|
|
333
333
|
'.': 'gray',
|
|
334
334
|
},
|
|
335
|
-
} as
|
|
335
|
+
} as Record<string, Record<string, string>>
|
|
336
336
|
|
|
337
337
|
// turn all supplied colors to hex colors which getContrastText from mui
|
|
338
338
|
// requires
|
|
@@ -347,7 +347,7 @@ export default transform(colorSchemes, ([key, val]) => [
|
|
|
347
347
|
// scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
|
|
348
348
|
// should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
|
|
349
349
|
export function getClustalXColor(
|
|
350
|
-
stats:
|
|
350
|
+
stats: Record<string, number>,
|
|
351
351
|
model: { columns: Record<string, string> },
|
|
352
352
|
row: string,
|
|
353
353
|
col: number,
|
|
@@ -494,7 +494,7 @@ export function getClustalXColor(
|
|
|
494
494
|
// scheme says there the jalview.org colorscheme says WLVIMAFCHP but it
|
|
495
495
|
// should be WLVIMAFCHPY, colprot.xml says e.g. %#ACFHILMVWYPp" which has Y
|
|
496
496
|
export function getPercentIdentityColor(
|
|
497
|
-
stats:
|
|
497
|
+
stats: Record<string, number>,
|
|
498
498
|
model: { columns: Record<string, string> },
|
|
499
499
|
row: string,
|
|
500
500
|
col: number,
|
|
@@ -504,10 +504,10 @@ export function getPercentIdentityColor(
|
|
|
504
504
|
const entries = Object.entries(stats)
|
|
505
505
|
let ent = 0
|
|
506
506
|
let letter = ''
|
|
507
|
-
for (
|
|
508
|
-
if (
|
|
509
|
-
letter =
|
|
510
|
-
ent =
|
|
507
|
+
for (const entry of entries) {
|
|
508
|
+
if (entry[1] > ent && entry[0] !== '-') {
|
|
509
|
+
letter = entry[0]
|
|
510
|
+
ent = entry[1]
|
|
511
511
|
}
|
|
512
512
|
}
|
|
513
513
|
const proportion = ent / total
|
|
@@ -1,201 +1,11 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
|
-
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
|
4
3
|
|
|
5
4
|
// locals
|
|
6
5
|
import { IBoxTrack, MsaViewModel } from '../model'
|
|
7
|
-
import
|
|
6
|
+
import BoxTrackBlock from './BoxTrackBlock'
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
start: number
|
|
11
|
-
end: number
|
|
12
|
-
}
|
|
13
|
-
const AnnotationBlock = observer(function ({
|
|
14
|
-
track,
|
|
15
|
-
model,
|
|
16
|
-
offsetX,
|
|
17
|
-
}: {
|
|
18
|
-
track: IBoxTrack
|
|
19
|
-
model: MsaViewModel
|
|
20
|
-
offsetX: number
|
|
21
|
-
}) {
|
|
22
|
-
const {
|
|
23
|
-
blockSize,
|
|
24
|
-
colWidth,
|
|
25
|
-
blanks,
|
|
26
|
-
rowHeight,
|
|
27
|
-
highResScaleFactor,
|
|
28
|
-
scrollX,
|
|
29
|
-
} = model
|
|
30
|
-
const {
|
|
31
|
-
model: { height, features, associatedRowName },
|
|
32
|
-
} = track
|
|
33
|
-
|
|
34
|
-
const feats: Feat[] = isStateTreeNode(features)
|
|
35
|
-
? // @ts-expect-error
|
|
36
|
-
getSnapshot(features)
|
|
37
|
-
: features
|
|
38
|
-
|
|
39
|
-
const layout = useMemo(() => {
|
|
40
|
-
const temp = new Layout()
|
|
41
|
-
|
|
42
|
-
feats?.forEach((feature, index) => {
|
|
43
|
-
const { start, end } = feature
|
|
44
|
-
if (associatedRowName) {
|
|
45
|
-
const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
|
|
46
|
-
const e = model.rowSpecificBpToPx(associatedRowName, end)
|
|
47
|
-
temp.addRect(`${index}`, s, e, rowHeight, feature)
|
|
48
|
-
} else {
|
|
49
|
-
const s = model.globalBpToPx(start - 1)
|
|
50
|
-
const e = model.globalBpToPx(end)
|
|
51
|
-
temp.addRect(`${index}`, s, e, rowHeight, feature)
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
return temp
|
|
55
|
-
|
|
56
|
-
// might convert to autorun based drawing
|
|
57
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58
|
-
}, [rowHeight, feats, associatedRowName, model, blanks])
|
|
59
|
-
|
|
60
|
-
const ref = useRef<HTMLCanvasElement>(null)
|
|
61
|
-
const labelRef = useRef<HTMLCanvasElement>(null)
|
|
62
|
-
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
if (!ref.current) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const ctx = ref.current.getContext('2d')
|
|
70
|
-
if (!ctx) {
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
ctx.resetTransform()
|
|
75
|
-
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
76
|
-
ctx.clearRect(0, 0, blockSize, height)
|
|
77
|
-
ctx.translate(-offsetX, 0)
|
|
78
|
-
ctx.textAlign = 'center'
|
|
79
|
-
ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
|
|
80
|
-
|
|
81
|
-
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
82
|
-
ctx.fillStyle = 'goldenrod'
|
|
83
|
-
layout.rectangles.forEach(value => {
|
|
84
|
-
const { minX, maxX, minY, maxY } = value
|
|
85
|
-
|
|
86
|
-
const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
|
|
87
|
-
const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
|
|
88
|
-
|
|
89
|
-
if (x2 - x1 > 0) {
|
|
90
|
-
ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}, [
|
|
94
|
-
associatedRowName,
|
|
95
|
-
blockSize,
|
|
96
|
-
colWidth,
|
|
97
|
-
layout.rectangles,
|
|
98
|
-
model,
|
|
99
|
-
rowHeight,
|
|
100
|
-
height,
|
|
101
|
-
offsetX,
|
|
102
|
-
highResScaleFactor,
|
|
103
|
-
features,
|
|
104
|
-
blanks,
|
|
105
|
-
])
|
|
106
|
-
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
if (!labelRef.current) {
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const ctx = labelRef.current.getContext('2d')
|
|
113
|
-
if (!ctx) {
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// this logic is very similar to MSACanvas
|
|
118
|
-
ctx.resetTransform()
|
|
119
|
-
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
120
|
-
ctx.clearRect(0, 0, blockSize, height)
|
|
121
|
-
ctx.translate(-offsetX, 0)
|
|
122
|
-
ctx.textAlign = 'center'
|
|
123
|
-
ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
|
|
124
|
-
|
|
125
|
-
ctx.fillStyle = 'black'
|
|
126
|
-
ctx.textAlign = 'left'
|
|
127
|
-
layout.rectangles.forEach(value => {
|
|
128
|
-
const { minX, maxX, maxY, minY } = value
|
|
129
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
-
const feature = value.data as any
|
|
131
|
-
|
|
132
|
-
const x1 = minX * colWidth
|
|
133
|
-
const x2 = maxX * colWidth
|
|
134
|
-
|
|
135
|
-
if (x2 - x1 > 0) {
|
|
136
|
-
const note = feature.attributes?.Note?.[0]
|
|
137
|
-
const name = feature.attributes?.Name?.[0]
|
|
138
|
-
const type = feature.type
|
|
139
|
-
ctx.fillText(
|
|
140
|
-
[type, name, note].filter(f => !!f).join(' - '),
|
|
141
|
-
Math.max(Math.min(-scrollX, x2), x1),
|
|
142
|
-
minY + (maxY - minY),
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
}, [
|
|
147
|
-
blockSize,
|
|
148
|
-
colWidth,
|
|
149
|
-
scrollX,
|
|
150
|
-
highResScaleFactor,
|
|
151
|
-
height,
|
|
152
|
-
layout.rectangles,
|
|
153
|
-
offsetX,
|
|
154
|
-
features,
|
|
155
|
-
model,
|
|
156
|
-
rowHeight,
|
|
157
|
-
blanks,
|
|
158
|
-
])
|
|
159
|
-
|
|
160
|
-
return !features ? null : (
|
|
161
|
-
<>
|
|
162
|
-
<canvas
|
|
163
|
-
ref={ref}
|
|
164
|
-
height={height * highResScaleFactor}
|
|
165
|
-
width={blockSize * highResScaleFactor}
|
|
166
|
-
style={{
|
|
167
|
-
position: 'absolute',
|
|
168
|
-
left: scrollX + offsetX,
|
|
169
|
-
width: blockSize,
|
|
170
|
-
height,
|
|
171
|
-
}}
|
|
172
|
-
/>
|
|
173
|
-
<canvas
|
|
174
|
-
ref={labelRef}
|
|
175
|
-
height={height * highResScaleFactor}
|
|
176
|
-
width={blockSize * highResScaleFactor}
|
|
177
|
-
style={{
|
|
178
|
-
position: 'absolute',
|
|
179
|
-
left: scrollX + offsetX,
|
|
180
|
-
width: blockSize,
|
|
181
|
-
height,
|
|
182
|
-
}}
|
|
183
|
-
/>
|
|
184
|
-
<canvas
|
|
185
|
-
ref={mouseoverRef}
|
|
186
|
-
height={height * highResScaleFactor}
|
|
187
|
-
width={blockSize * highResScaleFactor}
|
|
188
|
-
style={{
|
|
189
|
-
position: 'absolute',
|
|
190
|
-
left: scrollX + offsetX,
|
|
191
|
-
width: blockSize,
|
|
192
|
-
height,
|
|
193
|
-
}}
|
|
194
|
-
/>
|
|
195
|
-
</>
|
|
196
|
-
)
|
|
197
|
-
})
|
|
198
|
-
const AnnotationTrack = observer(function ({
|
|
8
|
+
const BoxTrack = observer(function ({
|
|
199
9
|
model,
|
|
200
10
|
track,
|
|
201
11
|
}: {
|
|
@@ -203,9 +13,7 @@ const AnnotationTrack = observer(function ({
|
|
|
203
13
|
track: IBoxTrack
|
|
204
14
|
}) {
|
|
205
15
|
const { blocksX, msaAreaWidth } = model
|
|
206
|
-
const {
|
|
207
|
-
model: { height },
|
|
208
|
-
} = track
|
|
16
|
+
const { height } = track.model
|
|
209
17
|
return (
|
|
210
18
|
<div
|
|
211
19
|
style={{
|
|
@@ -216,10 +24,10 @@ const AnnotationTrack = observer(function ({
|
|
|
216
24
|
}}
|
|
217
25
|
>
|
|
218
26
|
{blocksX.map(bx => (
|
|
219
|
-
<
|
|
27
|
+
<BoxTrackBlock track={track} key={bx} model={model} offsetX={bx} />
|
|
220
28
|
))}
|
|
221
29
|
</div>
|
|
222
30
|
)
|
|
223
31
|
})
|
|
224
32
|
|
|
225
|
-
export default
|
|
33
|
+
export default BoxTrack
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { useRef, useMemo, useEffect } from 'react'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
|
4
|
+
|
|
5
|
+
// locals
|
|
6
|
+
import { IBoxTrack, MsaViewModel } from '../model'
|
|
7
|
+
import Layout from '../layout'
|
|
8
|
+
|
|
9
|
+
interface Feat {
|
|
10
|
+
start: number
|
|
11
|
+
end: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const BoxTrackBlock = observer(function ({
|
|
15
|
+
track,
|
|
16
|
+
model,
|
|
17
|
+
offsetX,
|
|
18
|
+
}: {
|
|
19
|
+
track: IBoxTrack
|
|
20
|
+
model: MsaViewModel
|
|
21
|
+
offsetX: number
|
|
22
|
+
}) {
|
|
23
|
+
const {
|
|
24
|
+
blockSize,
|
|
25
|
+
colWidth,
|
|
26
|
+
blanks,
|
|
27
|
+
rowHeight,
|
|
28
|
+
highResScaleFactor,
|
|
29
|
+
scrollX,
|
|
30
|
+
} = model
|
|
31
|
+
const { height, features, associatedRowName } = track.model
|
|
32
|
+
|
|
33
|
+
const feats: Feat[] = isStateTreeNode(features)
|
|
34
|
+
? // @ts-expect-error
|
|
35
|
+
getSnapshot(features)
|
|
36
|
+
: features
|
|
37
|
+
|
|
38
|
+
const layout = useMemo(() => {
|
|
39
|
+
const temp = new Layout()
|
|
40
|
+
|
|
41
|
+
feats?.forEach((feature, index) => {
|
|
42
|
+
const { start, end } = feature
|
|
43
|
+
if (associatedRowName) {
|
|
44
|
+
const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
|
|
45
|
+
const e = model.rowSpecificBpToPx(associatedRowName, end)
|
|
46
|
+
temp.addRect(`${index}`, s, e, rowHeight, feature)
|
|
47
|
+
} else {
|
|
48
|
+
const s = model.globalBpToPx(start - 1)
|
|
49
|
+
const e = model.globalBpToPx(end)
|
|
50
|
+
temp.addRect(`${index}`, s, e, rowHeight, feature)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
return temp
|
|
54
|
+
|
|
55
|
+
// might convert to autorun based drawing
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, [rowHeight, feats, associatedRowName, model, blanks])
|
|
58
|
+
|
|
59
|
+
const ref = useRef<HTMLCanvasElement>(null)
|
|
60
|
+
const labelRef = useRef<HTMLCanvasElement>(null)
|
|
61
|
+
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!ref.current) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ctx = ref.current.getContext('2d')
|
|
69
|
+
if (!ctx) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ctx.resetTransform()
|
|
74
|
+
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
75
|
+
ctx.clearRect(0, 0, blockSize, height)
|
|
76
|
+
ctx.translate(-offsetX, 0)
|
|
77
|
+
ctx.textAlign = 'center'
|
|
78
|
+
ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
|
|
79
|
+
|
|
80
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
81
|
+
ctx.fillStyle = 'goldenrod'
|
|
82
|
+
layout.rectangles.forEach(value => {
|
|
83
|
+
const { minX, maxX, minY, maxY } = value
|
|
84
|
+
|
|
85
|
+
const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
|
|
86
|
+
const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
|
|
87
|
+
|
|
88
|
+
if (x2 - x1 > 0) {
|
|
89
|
+
ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}, [
|
|
93
|
+
associatedRowName,
|
|
94
|
+
blockSize,
|
|
95
|
+
colWidth,
|
|
96
|
+
layout.rectangles,
|
|
97
|
+
model,
|
|
98
|
+
rowHeight,
|
|
99
|
+
height,
|
|
100
|
+
offsetX,
|
|
101
|
+
highResScaleFactor,
|
|
102
|
+
features,
|
|
103
|
+
blanks,
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (!labelRef.current) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const ctx = labelRef.current.getContext('2d')
|
|
112
|
+
if (!ctx) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// this logic is very similar to MSACanvas
|
|
117
|
+
ctx.resetTransform()
|
|
118
|
+
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
119
|
+
ctx.clearRect(0, 0, blockSize, height)
|
|
120
|
+
ctx.translate(-offsetX, 0)
|
|
121
|
+
ctx.textAlign = 'center'
|
|
122
|
+
ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
|
|
123
|
+
|
|
124
|
+
ctx.fillStyle = 'black'
|
|
125
|
+
ctx.textAlign = 'left'
|
|
126
|
+
for (const value of layout.rectangles.values()) {
|
|
127
|
+
const { minX, maxX, maxY, minY, data } = value
|
|
128
|
+
|
|
129
|
+
const x1 = minX * colWidth
|
|
130
|
+
const x2 = maxX * colWidth
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
+
const feature = data as any
|
|
133
|
+
|
|
134
|
+
if (x2 - x1 > 0) {
|
|
135
|
+
const note = feature.attributes?.Note?.[0]
|
|
136
|
+
const name = feature.attributes?.Name?.[0]
|
|
137
|
+
const type = feature.type
|
|
138
|
+
ctx.fillText(
|
|
139
|
+
[type, name, note].filter(f => !!f).join(' - '),
|
|
140
|
+
Math.max(Math.min(-scrollX, x2), x1),
|
|
141
|
+
minY + (maxY - minY),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, [
|
|
146
|
+
blockSize,
|
|
147
|
+
colWidth,
|
|
148
|
+
scrollX,
|
|
149
|
+
highResScaleFactor,
|
|
150
|
+
height,
|
|
151
|
+
layout.rectangles,
|
|
152
|
+
offsetX,
|
|
153
|
+
features,
|
|
154
|
+
model,
|
|
155
|
+
rowHeight,
|
|
156
|
+
blanks,
|
|
157
|
+
])
|
|
158
|
+
|
|
159
|
+
return !features ? null : (
|
|
160
|
+
<>
|
|
161
|
+
<canvas
|
|
162
|
+
ref={ref}
|
|
163
|
+
height={height * highResScaleFactor}
|
|
164
|
+
width={blockSize * highResScaleFactor}
|
|
165
|
+
style={{
|
|
166
|
+
position: 'absolute',
|
|
167
|
+
left: scrollX + offsetX,
|
|
168
|
+
width: blockSize,
|
|
169
|
+
height,
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
172
|
+
<canvas
|
|
173
|
+
ref={labelRef}
|
|
174
|
+
height={height * highResScaleFactor}
|
|
175
|
+
width={blockSize * highResScaleFactor}
|
|
176
|
+
style={{
|
|
177
|
+
position: 'absolute',
|
|
178
|
+
left: scrollX + offsetX,
|
|
179
|
+
width: blockSize,
|
|
180
|
+
height,
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
<canvas
|
|
184
|
+
ref={mouseoverRef}
|
|
185
|
+
height={height * highResScaleFactor}
|
|
186
|
+
width={blockSize * highResScaleFactor}
|
|
187
|
+
style={{
|
|
188
|
+
position: 'absolute',
|
|
189
|
+
left: scrollX + offsetX,
|
|
190
|
+
width: blockSize,
|
|
191
|
+
height,
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
</>
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
export default BoxTrackBlock
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { IconButton,
|
|
1
|
+
import React, { Suspense, lazy, useState } from 'react'
|
|
2
|
+
import { IconButton, Typography } from '@mui/material'
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
5
|
// locals
|
|
6
6
|
import { MsaViewModel } from '../model'
|
|
7
|
-
import SettingsDialog from './SettingsDlg'
|
|
8
|
-
import AboutDialog from './AboutDlg'
|
|
9
|
-
import DetailsDialog from './DetailsDlg'
|
|
10
|
-
import TracklistDialog from './TracklistDlg'
|
|
11
7
|
|
|
12
8
|
// icons
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
9
|
+
import FolderOpen from '@mui/icons-material/FolderOpen'
|
|
10
|
+
import Settings from '@mui/icons-material/Settings'
|
|
11
|
+
import Help from '@mui/icons-material/Help'
|
|
12
|
+
import Assignment from '@mui/icons-material/Assignment'
|
|
13
|
+
import List from '@mui/icons-material/List'
|
|
14
|
+
import ZoomControls from './ZoomControls'
|
|
15
|
+
import MultiAlignmentSelector from './MultiAlignmentSelector'
|
|
16
|
+
|
|
17
|
+
const SettingsDialog = lazy(() => import('./dialogs/SettingsDlg'))
|
|
18
|
+
const AboutDialog = lazy(() => import('./dialogs/AboutDlg'))
|
|
19
|
+
const DetailsDialog = lazy(() => import('./dialogs/DetailsDlg'))
|
|
20
|
+
const TracklistDialog = lazy(() => import('./dialogs/TracklistDlg'))
|
|
18
21
|
|
|
19
22
|
const InfoArea = observer(({ model }: { model: MsaViewModel }) => {
|
|
20
23
|
const { mouseOverRowName, mouseCol } = model
|
|
@@ -32,7 +35,6 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
32
35
|
const [aboutDialogViz, setAboutDialogViz] = useState(false)
|
|
33
36
|
const [detailsDialogViz, setDetailsDialogViz] = useState(false)
|
|
34
37
|
const [tracklistDialogViz, setTracklistDialogViz] = useState(false)
|
|
35
|
-
const { currentAlignment, alignmentNames } = model
|
|
36
38
|
|
|
37
39
|
return (
|
|
38
40
|
<div style={{ display: 'flex' }}>
|
|
@@ -51,67 +53,54 @@ const Header = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
51
53
|
}
|
|
52
54
|
}}
|
|
53
55
|
>
|
|
54
|
-
<
|
|
56
|
+
<FolderOpen />
|
|
55
57
|
</IconButton>
|
|
56
58
|
<IconButton onClick={() => setSettingsDialogViz(true)}>
|
|
57
|
-
<
|
|
59
|
+
<Settings />
|
|
58
60
|
</IconButton>
|
|
59
61
|
<IconButton onClick={() => setDetailsDialogViz(true)}>
|
|
60
|
-
<
|
|
62
|
+
<Assignment />
|
|
61
63
|
</IconButton>
|
|
62
64
|
<IconButton onClick={() => setTracklistDialogViz(true)}>
|
|
63
|
-
<
|
|
65
|
+
<List />
|
|
64
66
|
</IconButton>
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
) : null}
|
|
67
|
+
<Suspense fallback={null}>
|
|
68
|
+
{settingsDialogViz ? (
|
|
69
|
+
<SettingsDialog
|
|
70
|
+
model={model}
|
|
71
|
+
onClose={() => setSettingsDialogViz(false)}
|
|
72
|
+
/>
|
|
73
|
+
) : null}
|
|
74
|
+
{aboutDialogViz ? (
|
|
75
|
+
<AboutDialog onClose={() => setAboutDialogViz(false)} />
|
|
76
|
+
) : null}
|
|
77
|
+
{detailsDialogViz ? (
|
|
78
|
+
<DetailsDialog
|
|
79
|
+
model={model}
|
|
80
|
+
onClose={() => setDetailsDialogViz(false)}
|
|
81
|
+
/>
|
|
82
|
+
) : null}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
native
|
|
93
|
-
value={currentAlignment}
|
|
94
|
-
size="small"
|
|
95
|
-
onChange={event => {
|
|
96
|
-
model.setCurrentAlignment(+(event.target.value as string))
|
|
97
|
-
model.setScrollX(0)
|
|
98
|
-
model.setScrollY(0)
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
101
|
-
{alignmentNames.map((option, index) => (
|
|
102
|
-
<option key={`${option}-${index}`} value={index}>
|
|
103
|
-
{option}
|
|
104
|
-
</option>
|
|
105
|
-
))}
|
|
106
|
-
</Select>
|
|
107
|
-
) : null}
|
|
84
|
+
{tracklistDialogViz ? (
|
|
85
|
+
<TracklistDialog
|
|
86
|
+
model={model}
|
|
87
|
+
onClose={() => setTracklistDialogViz(false)}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
90
|
+
</Suspense>
|
|
91
|
+
<MultiAlignmentSelector model={model} />
|
|
92
|
+
<ZoomControls model={model} />
|
|
108
93
|
<InfoArea model={model} />
|
|
109
|
-
<
|
|
94
|
+
<Spacer />
|
|
110
95
|
<IconButton onClick={() => setAboutDialogViz(true)}>
|
|
111
|
-
<
|
|
96
|
+
<Help />
|
|
112
97
|
</IconButton>
|
|
113
98
|
</div>
|
|
114
99
|
)
|
|
115
100
|
})
|
|
116
101
|
|
|
102
|
+
function Spacer() {
|
|
103
|
+
return <div style={{ flex: 1 }} />
|
|
104
|
+
}
|
|
105
|
+
|
|
117
106
|
export default Header
|