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.
- package/bundle/index.js +9 -9
- package/bundle/index.js.LICENSE.txt +8 -8
- package/bundle/index.js.map +1 -1
- package/dist/colorSchemes.d.ts +0 -6
- package/dist/colorSchemes.js +1 -119
- package/dist/colorSchemes.js.map +1 -1
- package/dist/components/ConservationTrack.d.ts +8 -0
- package/dist/components/ConservationTrack.js +54 -0
- package/dist/components/ConservationTrack.js.map +1 -0
- package/dist/components/Loading.js +14 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +36 -0
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -2
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.d.ts +3 -3
- package/dist/components/TextTrack.js +4 -1
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +21 -8
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +19 -3
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/header/GappynessSlider.d.ts +6 -0
- package/dist/components/header/GappynessSlider.js +19 -0
- package/dist/components/header/GappynessSlider.js.map +1 -0
- package/dist/components/header/Header.js +3 -1
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderMenu.js +30 -14
- package/dist/components/header/HeaderMenu.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +4 -3
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +56 -42
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +53 -10
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/tracks/renderTracksSvg.d.ts +29 -0
- package/dist/components/tracks/renderTracksSvg.js +83 -0
- package/dist/components/tracks/renderTracksSvg.js.map +1 -0
- package/dist/components/tree/TreeNodeMenu.js +2 -2
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +1 -1
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +22 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -0
- package/dist/layout.js.map +1 -1
- package/dist/model/msaModel.js +3 -2
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.js +9 -8
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +256 -15
- package/dist/model.js +408 -128
- package/dist/model.js.map +1 -1
- package/dist/neighborJoining.d.ts +1 -0
- package/dist/neighborJoining.js +839 -0
- package/dist/neighborJoining.js.map +1 -0
- package/dist/neighborJoining.test.d.ts +1 -0
- package/dist/neighborJoining.test.js +110 -0
- package/dist/neighborJoining.test.js.map +1 -0
- package/dist/parsers/A3mMSA.d.ts +43 -0
- package/dist/parsers/A3mMSA.js +277 -0
- package/dist/parsers/A3mMSA.js.map +1 -0
- package/dist/parsers/A3mMSA.test.d.ts +1 -0
- package/dist/parsers/A3mMSA.test.js +138 -0
- package/dist/parsers/A3mMSA.test.js.map +1 -0
- package/dist/parsers/ClustalMSA.d.ts +4 -4
- package/dist/parsers/ClustalMSA.js +3 -1
- package/dist/parsers/ClustalMSA.js.map +1 -1
- package/dist/parsers/FastaMSA.js +17 -16
- package/dist/parsers/FastaMSA.js.map +1 -1
- package/dist/renderToSvg.d.ts +1 -0
- package/dist/renderToSvg.js +48 -18
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.js +2 -0
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/types.d.ts +2 -3
- package/dist/util.js +17 -9
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/src/colorSchemes.ts +1 -179
- package/src/components/ConservationTrack.tsx +104 -0
- package/src/components/Loading.tsx +44 -2
- package/src/components/MSAView.tsx +68 -0
- package/src/components/SequenceTextArea.tsx +3 -2
- package/src/components/TextTrack.tsx +7 -4
- package/src/components/Track.tsx +25 -9
- package/src/components/dialogs/ExportSVGDialog.tsx +25 -1
- package/src/components/header/GappynessSlider.tsx +35 -0
- package/src/components/header/Header.tsx +3 -1
- package/src/components/header/HeaderMenu.tsx +36 -15
- package/src/components/minimap/MinimapSVG.tsx +6 -3
- package/src/components/msa/MSACanvasBlock.tsx +66 -48
- package/src/components/msa/renderMSABlock.ts +82 -22
- package/src/components/tracks/renderTracksSvg.ts +157 -0
- package/src/components/tree/TreeNodeMenu.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +1 -1
- package/src/constants.ts +27 -0
- package/src/layout.ts +1 -6
- package/src/model/msaModel.ts +4 -2
- package/src/model/treeModel.ts +19 -8
- package/src/model.ts +496 -140
- package/src/neighborJoining.test.ts +129 -0
- package/src/neighborJoining.ts +885 -0
- package/src/parsers/A3mMSA.test.ts +164 -0
- package/src/parsers/A3mMSA.ts +321 -0
- package/src/parsers/ClustalMSA.ts +7 -5
- package/src/parsers/FastaMSA.ts +17 -17
- package/src/renderToSvg.tsx +105 -26
- package/src/rowCoordinateCalculations.ts +2 -0
- package/src/types.ts +2 -4
- package/src/util.ts +21 -8
- package/src/version.ts +1 -1
- package/dist/components/dialogs/TracklistDialog.d.ts +0 -7
- package/dist/components/dialogs/TracklistDialog.js +0 -23
- package/dist/components/dialogs/TracklistDialog.js.map +0 -1
- package/src/components/dialogs/TracklistDialog.tsx +0 -73
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef } from 'react'
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { BaseTooltip } from '@jbrowse/core/ui'
|
|
3
4
|
import { useTheme } from '@mui/material'
|
|
4
5
|
import { autorun } from 'mobx'
|
|
5
6
|
import { observer } from 'mobx-react'
|
|
@@ -79,55 +80,72 @@ const MSACanvasBlock = observer(function ({
|
|
|
79
80
|
contrastScheme,
|
|
80
81
|
])
|
|
81
82
|
|
|
83
|
+
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>()
|
|
84
|
+
const { hoveredInsertion } = model
|
|
85
|
+
|
|
82
86
|
return (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
<>
|
|
88
|
+
<canvas
|
|
89
|
+
ref={ref}
|
|
90
|
+
onMouseMove={event => {
|
|
91
|
+
if (!ref.current) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
setMousePosition({ x: event.clientX, y: event.clientY })
|
|
95
|
+
const { left, top } = ref.current.getBoundingClientRect()
|
|
96
|
+
const mouseX = event.clientX - left + offsetX
|
|
97
|
+
const mouseY = event.clientY - top + offsetY
|
|
98
|
+
const x = Math.floor(mouseX / colWidth)
|
|
99
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
101
|
+
// Only set mouse position if within valid MSA bounds
|
|
102
|
+
if (x >= 0 && x < model.numColumns && y >= 0 && y < model.numRows) {
|
|
103
|
+
model.setMousePos(x, y)
|
|
104
|
+
} else {
|
|
105
|
+
// Clear mouse position when outside bounds
|
|
106
|
+
model.setMousePos(undefined, undefined)
|
|
107
|
+
}
|
|
108
|
+
}}
|
|
109
|
+
onClick={event => {
|
|
110
|
+
if (!ref.current) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
const { left, top } = ref.current.getBoundingClientRect()
|
|
114
|
+
const mouseX = event.clientX - left + offsetX
|
|
115
|
+
const mouseY = event.clientY - top + offsetY
|
|
116
|
+
const x = Math.floor(mouseX / colWidth)
|
|
117
|
+
const y = Math.floor(mouseY / rowHeight)
|
|
118
|
+
if (x === mouseClickCol && y === mouseClickRow) {
|
|
119
|
+
model.setMouseClickPos(undefined, undefined)
|
|
120
|
+
} else {
|
|
121
|
+
model.setMouseClickPos(x, y)
|
|
122
|
+
}
|
|
123
|
+
}}
|
|
124
|
+
onMouseLeave={() => {
|
|
125
|
+
model.setMousePos()
|
|
126
|
+
setMousePosition(undefined)
|
|
127
|
+
}}
|
|
128
|
+
width={blockSize * highResScaleFactor}
|
|
129
|
+
height={blockSize * highResScaleFactor}
|
|
130
|
+
style={{
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
top: scrollY + offsetY,
|
|
133
|
+
left: scrollX + offsetX,
|
|
134
|
+
width: blockSize,
|
|
135
|
+
height: blockSize,
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
{hoveredInsertion && mousePosition ? (
|
|
139
|
+
<BaseTooltip
|
|
140
|
+
clientPoint={{ x: mousePosition.x, y: mousePosition.y + 15 }}
|
|
141
|
+
>
|
|
142
|
+
Insertion ({hoveredInsertion.letters.length}bp):{' '}
|
|
143
|
+
{hoveredInsertion.letters.length > 20
|
|
144
|
+
? `${hoveredInsertion.letters.slice(0, 20)}...`
|
|
145
|
+
: hoveredInsertion.letters}
|
|
146
|
+
</BaseTooltip>
|
|
147
|
+
) : null}
|
|
148
|
+
</>
|
|
131
149
|
)
|
|
132
150
|
})
|
|
133
151
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getClustalXColor, getPercentIdentityColor } from '../../colorSchemes'
|
|
2
|
-
|
|
3
1
|
import type { MsaViewModel } from '../../model'
|
|
4
2
|
import type { NodeWithIdsAndLength } from '../../types'
|
|
5
3
|
import type { Theme } from '@mui/material'
|
|
@@ -57,7 +55,6 @@ export function renderMSABlock({
|
|
|
57
55
|
ctx,
|
|
58
56
|
theme,
|
|
59
57
|
offsetX,
|
|
60
|
-
offsetY,
|
|
61
58
|
xStart,
|
|
62
59
|
xEnd,
|
|
63
60
|
visibleLeaves,
|
|
@@ -73,6 +70,13 @@ export function renderMSABlock({
|
|
|
73
70
|
xEnd,
|
|
74
71
|
visibleLeaves,
|
|
75
72
|
})
|
|
73
|
+
drawInsertionIndicators({
|
|
74
|
+
model,
|
|
75
|
+
ctx,
|
|
76
|
+
xStart,
|
|
77
|
+
xEnd,
|
|
78
|
+
visibleLeaves,
|
|
79
|
+
})
|
|
76
80
|
ctx.resetTransform()
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -88,7 +92,6 @@ function drawTiles({
|
|
|
88
92
|
model: MsaViewModel
|
|
89
93
|
offsetX: number
|
|
90
94
|
theme: Theme
|
|
91
|
-
offsetY: number
|
|
92
95
|
ctx: CanvasRenderingContext2D
|
|
93
96
|
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
94
97
|
xStart: number
|
|
@@ -127,25 +130,12 @@ function drawTiles({
|
|
|
127
130
|
const r1 = colorSchemeName === 'clustalx_protein_dynamic'
|
|
128
131
|
const r2 = colorSchemeName === 'percent_identity_dynamic'
|
|
129
132
|
const color = r1
|
|
130
|
-
?
|
|
131
|
-
// use model.colStats dot notation here: delay use of colStats
|
|
132
|
-
// until absolutely needed
|
|
133
|
-
model.colStats[xStart + i]!,
|
|
134
|
-
model.colStatsSums[xStart + i]!,
|
|
135
|
-
model,
|
|
136
|
-
name,
|
|
137
|
-
xStart + i,
|
|
138
|
-
)
|
|
133
|
+
? model.colClustalX[xStart + i]![letter]
|
|
139
134
|
: r2
|
|
140
|
-
?
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
model.colStatsSums[xStart + i]!,
|
|
145
|
-
model,
|
|
146
|
-
name,
|
|
147
|
-
xStart + i,
|
|
148
|
-
)
|
|
135
|
+
? (() => {
|
|
136
|
+
const consensus = model.colConsensus[xStart + i]!
|
|
137
|
+
return letter === consensus.letter ? consensus.color : undefined
|
|
138
|
+
})()
|
|
149
139
|
: colorScheme[letter.toUpperCase()]
|
|
150
140
|
if (bgColor || r1 || r2) {
|
|
151
141
|
// Use a very light background for matching positions in relative mode
|
|
@@ -237,3 +227,73 @@ function drawText({
|
|
|
237
227
|
}
|
|
238
228
|
}
|
|
239
229
|
}
|
|
230
|
+
|
|
231
|
+
function drawInsertionIndicators({
|
|
232
|
+
model,
|
|
233
|
+
ctx,
|
|
234
|
+
visibleLeaves,
|
|
235
|
+
xStart,
|
|
236
|
+
xEnd,
|
|
237
|
+
}: {
|
|
238
|
+
model: MsaViewModel
|
|
239
|
+
ctx: CanvasRenderingContext2D
|
|
240
|
+
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
241
|
+
xStart: number
|
|
242
|
+
xEnd: number
|
|
243
|
+
}) {
|
|
244
|
+
const { bgColor, hideGapsEffective } = model
|
|
245
|
+
if (!hideGapsEffective) {
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
ctx.lineWidth = 1
|
|
250
|
+
ctx.strokeStyle = '#f0f'
|
|
251
|
+
drawZigZag({ visibleLeaves, xStart, ctx, model, xEnd, offset: 0 })
|
|
252
|
+
ctx.strokeStyle = !bgColor ? '#000' : '#fff'
|
|
253
|
+
drawZigZag({ visibleLeaves, xStart, ctx, model, xEnd, offset: -1 })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function drawZigZag({
|
|
257
|
+
model,
|
|
258
|
+
ctx,
|
|
259
|
+
visibleLeaves,
|
|
260
|
+
xStart,
|
|
261
|
+
xEnd,
|
|
262
|
+
offset,
|
|
263
|
+
}: {
|
|
264
|
+
model: MsaViewModel
|
|
265
|
+
ctx: CanvasRenderingContext2D
|
|
266
|
+
visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
|
|
267
|
+
xStart: number
|
|
268
|
+
xEnd: number
|
|
269
|
+
offset: number
|
|
270
|
+
}) {
|
|
271
|
+
const zigSize = 1
|
|
272
|
+
const { colWidth, rowHeight, insertionPositions } = model
|
|
273
|
+
for (const node of visibleLeaves) {
|
|
274
|
+
const { name } = node.data
|
|
275
|
+
const insertions = insertionPositions.get(name)
|
|
276
|
+
if (insertions) {
|
|
277
|
+
const y = node.x!
|
|
278
|
+
for (const { pos } of insertions) {
|
|
279
|
+
if (pos >= xStart && pos < xEnd) {
|
|
280
|
+
const x = pos * colWidth
|
|
281
|
+
const top = y - rowHeight
|
|
282
|
+
const bottom = y
|
|
283
|
+
ctx.beginPath()
|
|
284
|
+
ctx.moveTo(x + offset, top + offset)
|
|
285
|
+
let currentY = top
|
|
286
|
+
let goRight = true
|
|
287
|
+
while (currentY < bottom) {
|
|
288
|
+
const nextY = Math.min(currentY + zigSize * 2, bottom)
|
|
289
|
+
const nextX = goRight ? x + zigSize : x - zigSize
|
|
290
|
+
ctx.lineTo(nextX + offset, nextY + offset)
|
|
291
|
+
currentY = nextY
|
|
292
|
+
goRight = !goRight
|
|
293
|
+
}
|
|
294
|
+
ctx.stroke()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { MsaViewModel } from '../../model'
|
|
2
|
+
import type { BasicTrack } from '../../types'
|
|
3
|
+
|
|
4
|
+
export function renderConservationTrack({
|
|
5
|
+
model,
|
|
6
|
+
ctx,
|
|
7
|
+
offsetX,
|
|
8
|
+
offsetY,
|
|
9
|
+
trackHeight,
|
|
10
|
+
blockSizeXOverride,
|
|
11
|
+
highResScaleFactorOverride,
|
|
12
|
+
}: {
|
|
13
|
+
model: MsaViewModel
|
|
14
|
+
ctx: CanvasRenderingContext2D
|
|
15
|
+
offsetX: number
|
|
16
|
+
offsetY: number
|
|
17
|
+
trackHeight: number
|
|
18
|
+
blockSizeXOverride?: number
|
|
19
|
+
highResScaleFactorOverride?: number
|
|
20
|
+
}) {
|
|
21
|
+
const { blockSize, colWidth, highResScaleFactor, conservation } = model
|
|
22
|
+
const bx = blockSizeXOverride ?? blockSize
|
|
23
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
24
|
+
|
|
25
|
+
ctx.resetTransform()
|
|
26
|
+
ctx.scale(k, k)
|
|
27
|
+
ctx.translate(-offsetX, offsetY)
|
|
28
|
+
|
|
29
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
30
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
31
|
+
|
|
32
|
+
for (let i = xStart; i < xEnd && i < conservation.length; i++) {
|
|
33
|
+
const value = conservation[i]!
|
|
34
|
+
const barHeight = value * trackHeight
|
|
35
|
+
const x = i * colWidth
|
|
36
|
+
|
|
37
|
+
const hue = value * 120
|
|
38
|
+
ctx.fillStyle = `hsl(${hue}, 70%, 50%)`
|
|
39
|
+
ctx.fillRect(x, trackHeight - barHeight, colWidth, barHeight)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ctx.resetTransform()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderTextTrack({
|
|
46
|
+
model,
|
|
47
|
+
ctx,
|
|
48
|
+
track,
|
|
49
|
+
offsetX,
|
|
50
|
+
offsetY,
|
|
51
|
+
contrastScheme,
|
|
52
|
+
blockSizeXOverride,
|
|
53
|
+
highResScaleFactorOverride,
|
|
54
|
+
}: {
|
|
55
|
+
model: MsaViewModel
|
|
56
|
+
ctx: CanvasRenderingContext2D
|
|
57
|
+
track: BasicTrack
|
|
58
|
+
offsetX: number
|
|
59
|
+
offsetY: number
|
|
60
|
+
contrastScheme: Record<string, string>
|
|
61
|
+
blockSizeXOverride?: number
|
|
62
|
+
highResScaleFactorOverride?: number
|
|
63
|
+
}) {
|
|
64
|
+
const {
|
|
65
|
+
blockSize,
|
|
66
|
+
bgColor,
|
|
67
|
+
colorScheme: modelColorScheme,
|
|
68
|
+
colWidth,
|
|
69
|
+
fontSize,
|
|
70
|
+
rowHeight,
|
|
71
|
+
highResScaleFactor,
|
|
72
|
+
} = model
|
|
73
|
+
|
|
74
|
+
const { customColorScheme, data } = track.model
|
|
75
|
+
const colorScheme = customColorScheme ?? modelColorScheme
|
|
76
|
+
const bx = blockSizeXOverride ?? blockSize
|
|
77
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
78
|
+
|
|
79
|
+
ctx.resetTransform()
|
|
80
|
+
ctx.scale(k, k)
|
|
81
|
+
ctx.translate(-offsetX, offsetY)
|
|
82
|
+
ctx.textAlign = 'center'
|
|
83
|
+
ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
|
|
84
|
+
|
|
85
|
+
const xStart = Math.max(0, Math.floor(offsetX / colWidth))
|
|
86
|
+
const xEnd = Math.max(0, Math.ceil((offsetX + bx) / colWidth))
|
|
87
|
+
const str = data?.slice(xStart, xEnd)
|
|
88
|
+
|
|
89
|
+
for (let i = 0; str && i < str.length; i++) {
|
|
90
|
+
const letter = str[i]!
|
|
91
|
+
const color = colorScheme[letter.toUpperCase()]
|
|
92
|
+
const x = i * colWidth + offsetX - (offsetX % colWidth)
|
|
93
|
+
|
|
94
|
+
if (bgColor && color) {
|
|
95
|
+
ctx.fillStyle = color
|
|
96
|
+
ctx.fillRect(x, 0, colWidth, rowHeight)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
|
|
100
|
+
ctx.fillStyle =
|
|
101
|
+
bgColor && color
|
|
102
|
+
? (contrastScheme[letter.toUpperCase()] ?? 'black')
|
|
103
|
+
: 'black'
|
|
104
|
+
ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.resetTransform()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function renderAllTracks({
|
|
112
|
+
model,
|
|
113
|
+
ctx,
|
|
114
|
+
offsetX,
|
|
115
|
+
contrastScheme,
|
|
116
|
+
blockSizeXOverride,
|
|
117
|
+
highResScaleFactorOverride,
|
|
118
|
+
}: {
|
|
119
|
+
model: MsaViewModel
|
|
120
|
+
ctx: CanvasRenderingContext2D
|
|
121
|
+
offsetX: number
|
|
122
|
+
contrastScheme: Record<string, string>
|
|
123
|
+
blockSizeXOverride?: number
|
|
124
|
+
highResScaleFactorOverride?: number
|
|
125
|
+
}) {
|
|
126
|
+
const { turnedOnTracks } = model
|
|
127
|
+
let currentY = 0
|
|
128
|
+
|
|
129
|
+
for (const track of turnedOnTracks) {
|
|
130
|
+
const trackHeight = track.model.height
|
|
131
|
+
|
|
132
|
+
if (track.model.id === 'conservation') {
|
|
133
|
+
renderConservationTrack({
|
|
134
|
+
model,
|
|
135
|
+
ctx,
|
|
136
|
+
offsetX,
|
|
137
|
+
offsetY: currentY,
|
|
138
|
+
trackHeight,
|
|
139
|
+
blockSizeXOverride,
|
|
140
|
+
highResScaleFactorOverride,
|
|
141
|
+
})
|
|
142
|
+
} else {
|
|
143
|
+
renderTextTrack({
|
|
144
|
+
model,
|
|
145
|
+
ctx,
|
|
146
|
+
track,
|
|
147
|
+
offsetX,
|
|
148
|
+
offsetY: currentY,
|
|
149
|
+
contrastScheme,
|
|
150
|
+
blockSizeXOverride,
|
|
151
|
+
highResScaleFactorOverride,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
currentY += trackHeight
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -64,9 +64,9 @@ const TreeMenu = observer(function ({
|
|
|
64
64
|
model.toggleCollapsed(node.id)
|
|
65
65
|
} else {
|
|
66
66
|
if (node.id.endsWith('-leafnode')) {
|
|
67
|
-
model.
|
|
67
|
+
model.toggleCollapsedLeaf(node.id)
|
|
68
68
|
} else {
|
|
69
|
-
model.
|
|
69
|
+
model.toggleCollapsedLeaf(`${node.id}-leafnode`)
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
onClose()
|
|
@@ -107,7 +107,7 @@ export function renderNodeBubbles({
|
|
|
107
107
|
// @ts-expect-error
|
|
108
108
|
const { [val]: x, data } = node
|
|
109
109
|
const y = node.x!
|
|
110
|
-
const { id
|
|
110
|
+
const { id, name } = data
|
|
111
111
|
if (
|
|
112
112
|
node.height > 1 &&
|
|
113
113
|
y > offsetY - extendBounds &&
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Main model defaults
|
|
2
|
+
export const defaultRowHeight = 16
|
|
3
|
+
export const defaultColWidth = 12
|
|
4
|
+
export const defaultHeight = 550
|
|
5
|
+
export const defaultScrollX = 0
|
|
6
|
+
export const defaultScrollY = 0
|
|
7
|
+
export const defaultCurrentAlignment = 0
|
|
8
|
+
export const defaultShowDomains = false
|
|
9
|
+
export const defaultHideGaps = true
|
|
10
|
+
export const defaultAllowedGappyness = 100
|
|
11
|
+
export const defaultContrastLettering = true
|
|
12
|
+
export const defaultSubFeatureRows = false
|
|
13
|
+
export const defaultDrawMsaLetters = true
|
|
14
|
+
|
|
15
|
+
// MSA model defaults
|
|
16
|
+
export const defaultBgColor = true
|
|
17
|
+
export const defaultColorSchemeName = 'maeditor'
|
|
18
|
+
|
|
19
|
+
// Tree model defaults
|
|
20
|
+
export const defaultDrawLabels = true
|
|
21
|
+
export const defaultLabelsAlignRight = false
|
|
22
|
+
export const defaultTreeAreaWidth = 400
|
|
23
|
+
export const defaultTreeWidth = 300
|
|
24
|
+
export const defaultTreeWidthMatchesArea = true
|
|
25
|
+
export const defaultShowBranchLen = true
|
|
26
|
+
export const defaultDrawTree = true
|
|
27
|
+
export const defaultDrawNodeBubbles = true
|
package/src/layout.ts
CHANGED
|
@@ -63,12 +63,7 @@ export default class Layout {
|
|
|
63
63
|
return false
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const results = this.flatbush.search(
|
|
67
|
-
box.minX,
|
|
68
|
-
box.minY,
|
|
69
|
-
box.maxX,
|
|
70
|
-
box.maxY,
|
|
71
|
-
)
|
|
66
|
+
const results = this.flatbush.search(box.minX, box.minY, box.maxX, box.maxY)
|
|
72
67
|
|
|
73
68
|
return results.length > 0
|
|
74
69
|
}
|
package/src/model/msaModel.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { types } from 'mobx-state-tree'
|
|
2
2
|
|
|
3
|
+
import { defaultBgColor, defaultColorSchemeName } from '../constants'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* #stateModel MSAModel
|
|
5
7
|
*/
|
|
@@ -12,13 +14,13 @@ export function MSAModelF() {
|
|
|
12
14
|
* #property
|
|
13
15
|
* draw MSA tiles with a background color
|
|
14
16
|
*/
|
|
15
|
-
bgColor:
|
|
17
|
+
bgColor: defaultBgColor,
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* #property
|
|
19
21
|
* default color scheme name
|
|
20
22
|
*/
|
|
21
|
-
colorSchemeName:
|
|
23
|
+
colorSchemeName: defaultColorSchemeName,
|
|
22
24
|
})
|
|
23
25
|
.actions(self => ({
|
|
24
26
|
/**
|
package/src/model/treeModel.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { types } from 'mobx-state-tree'
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
defaultDrawLabels,
|
|
5
|
+
defaultDrawNodeBubbles,
|
|
6
|
+
defaultDrawTree,
|
|
7
|
+
defaultLabelsAlignRight,
|
|
8
|
+
defaultShowBranchLen,
|
|
9
|
+
defaultTreeAreaWidth,
|
|
10
|
+
defaultTreeWidth,
|
|
11
|
+
defaultTreeWidthMatchesArea,
|
|
12
|
+
} from '../constants'
|
|
13
|
+
|
|
3
14
|
/**
|
|
4
15
|
* #stateModel Tree
|
|
5
16
|
*/
|
|
@@ -11,30 +22,30 @@ export function TreeModelF() {
|
|
|
11
22
|
/**
|
|
12
23
|
* #property
|
|
13
24
|
*/
|
|
14
|
-
drawLabels:
|
|
25
|
+
drawLabels: defaultDrawLabels,
|
|
15
26
|
/**
|
|
16
27
|
* #property
|
|
17
28
|
* right-align the labels
|
|
18
29
|
*/
|
|
19
|
-
labelsAlignRight:
|
|
30
|
+
labelsAlignRight: defaultLabelsAlignRight,
|
|
20
31
|
|
|
21
32
|
/**
|
|
22
33
|
* #property
|
|
23
34
|
* width of the area the tree is drawn in, px
|
|
24
35
|
*/
|
|
25
|
-
treeAreaWidth: types.optional(types.number,
|
|
36
|
+
treeAreaWidth: types.optional(types.number, defaultTreeAreaWidth),
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
39
|
* #property
|
|
29
40
|
* width of the tree within the treeArea, px
|
|
30
41
|
*/
|
|
31
|
-
treeWidth: types.optional(types.number,
|
|
42
|
+
treeWidth: types.optional(types.number, defaultTreeWidth),
|
|
32
43
|
|
|
33
44
|
/**
|
|
34
45
|
* #getter
|
|
35
46
|
* synchronization that matches treeWidth to treeAreaWidth
|
|
36
47
|
*/
|
|
37
|
-
treeWidthMatchesArea:
|
|
48
|
+
treeWidthMatchesArea: defaultTreeWidthMatchesArea,
|
|
38
49
|
|
|
39
50
|
/**
|
|
40
51
|
* #property
|
|
@@ -42,19 +53,19 @@ export function TreeModelF() {
|
|
|
42
53
|
* lengths. if false, the layout is a "cladogram" that does not take into
|
|
43
54
|
* account evolutionary distances
|
|
44
55
|
*/
|
|
45
|
-
showBranchLen:
|
|
56
|
+
showBranchLen: defaultShowBranchLen,
|
|
46
57
|
|
|
47
58
|
/**
|
|
48
59
|
* #property
|
|
49
60
|
* draw tree, boolean
|
|
50
61
|
*/
|
|
51
|
-
drawTree:
|
|
62
|
+
drawTree: defaultDrawTree,
|
|
52
63
|
|
|
53
64
|
/**
|
|
54
65
|
* #property
|
|
55
66
|
* draw clickable node bubbles on the tree
|
|
56
67
|
*/
|
|
57
|
-
drawNodeBubbles:
|
|
68
|
+
drawNodeBubbles: defaultDrawNodeBubbles,
|
|
58
69
|
})
|
|
59
70
|
.actions(self => ({
|
|
60
71
|
/**
|