@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.8
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/dist/index.esm.js +2371 -1642
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +4387 -2952
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +33 -13
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +95 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +9 -3
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +3 -1
- package/src/components/CreateApolloAnnotation.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +162 -116
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +152 -0
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
2
|
+
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
3
|
+
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
|
|
4
|
+
import {
|
|
5
|
+
type Frame,
|
|
6
|
+
defaultCodonTable,
|
|
7
|
+
getFrame,
|
|
8
|
+
revcom,
|
|
9
|
+
} from '@jbrowse/core/util'
|
|
10
|
+
import { type Theme, createTheme } from '@mui/material'
|
|
11
|
+
import { autorun } from 'mobx'
|
|
12
|
+
import { type Instance, addDisposer } from 'mobx-state-tree'
|
|
13
|
+
|
|
14
|
+
import { type ApolloSessionModel } from '../../session'
|
|
15
|
+
|
|
16
|
+
import { baseModelFactory } from './base'
|
|
17
|
+
|
|
18
|
+
function colorCode(letter: string, theme: Theme) {
|
|
19
|
+
return (
|
|
20
|
+
theme.palette.bases[
|
|
21
|
+
letter.toUpperCase() as keyof Theme['palette']['bases']
|
|
22
|
+
].main.toString() ?? 'lightgray'
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function codonColorCode(letter: string, highContrast?: boolean) {
|
|
27
|
+
const colorMap: Record<string, string | undefined> = {
|
|
28
|
+
M: '#33ee33',
|
|
29
|
+
'*': highContrast ? '#000000' : '#f44336',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return colorMap[letter.toUpperCase()]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function reverseCodonSeq(seq: string): string {
|
|
36
|
+
// disable because sequence is all ascii
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-spread
|
|
38
|
+
return [...seq]
|
|
39
|
+
.map((c) => revcom(c))
|
|
40
|
+
.reverse()
|
|
41
|
+
.join('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function drawLetter(
|
|
45
|
+
seqTrackctx: CanvasRenderingContext2D,
|
|
46
|
+
startPx: number,
|
|
47
|
+
widthPx: number,
|
|
48
|
+
letter: string,
|
|
49
|
+
textY: number,
|
|
50
|
+
) {
|
|
51
|
+
const fontSize = Math.min(widthPx, 10)
|
|
52
|
+
seqTrackctx.fillStyle = '#000'
|
|
53
|
+
seqTrackctx.font = `${fontSize}px`
|
|
54
|
+
const textWidth = seqTrackctx.measureText(letter).width
|
|
55
|
+
const textX = startPx + (widthPx - textWidth) / 2
|
|
56
|
+
seqTrackctx.fillText(letter, textX, textY + 10)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function drawTranslation(
|
|
60
|
+
seqTrackctx: CanvasRenderingContext2D,
|
|
61
|
+
bpPerPx: number,
|
|
62
|
+
trnslStartPx: number,
|
|
63
|
+
trnslY: number,
|
|
64
|
+
trnslWidthPx: number,
|
|
65
|
+
sequenceRowHeight: number,
|
|
66
|
+
seq: string,
|
|
67
|
+
i: number,
|
|
68
|
+
reverse: boolean,
|
|
69
|
+
showStartCodons: boolean,
|
|
70
|
+
showStopCodons: boolean,
|
|
71
|
+
highContrast: boolean,
|
|
72
|
+
) {
|
|
73
|
+
let codonSeq: string = seq.slice(i, i + 3).toUpperCase()
|
|
74
|
+
if (reverse) {
|
|
75
|
+
codonSeq = reverseCodonSeq(codonSeq)
|
|
76
|
+
}
|
|
77
|
+
const codonLetter =
|
|
78
|
+
defaultCodonTable[codonSeq as keyof typeof defaultCodonTable]
|
|
79
|
+
if (!codonLetter) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
const fillColor = codonColorCode(codonLetter, highContrast)
|
|
83
|
+
if (
|
|
84
|
+
fillColor &&
|
|
85
|
+
((showStopCodons && codonLetter == '*') ||
|
|
86
|
+
(showStartCodons && codonLetter != '*'))
|
|
87
|
+
) {
|
|
88
|
+
seqTrackctx.fillStyle = fillColor
|
|
89
|
+
seqTrackctx.fillRect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight)
|
|
90
|
+
}
|
|
91
|
+
if (bpPerPx <= 0.1) {
|
|
92
|
+
seqTrackctx.rect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight)
|
|
93
|
+
seqTrackctx.stroke()
|
|
94
|
+
drawLetter(seqTrackctx, trnslStartPx, trnslWidthPx, codonLetter, trnslY)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getTranslationRow(frame: Frame, bpPerPx: number) {
|
|
99
|
+
const offset = bpPerPx <= 1 ? 2 : 0
|
|
100
|
+
switch (frame) {
|
|
101
|
+
case 3: {
|
|
102
|
+
return 0
|
|
103
|
+
}
|
|
104
|
+
case 2: {
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
case 1: {
|
|
108
|
+
return 2
|
|
109
|
+
}
|
|
110
|
+
case -1: {
|
|
111
|
+
return 3 + offset
|
|
112
|
+
}
|
|
113
|
+
case -2: {
|
|
114
|
+
return 4 + offset
|
|
115
|
+
}
|
|
116
|
+
case -3: {
|
|
117
|
+
return 5 + offset
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getSeqRow(
|
|
123
|
+
strand: 1 | -1 | undefined,
|
|
124
|
+
bpPerPx: number,
|
|
125
|
+
): number | undefined {
|
|
126
|
+
if (bpPerPx > 1 || strand === undefined) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
return strand === 1 ? 3 : 4
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function highlightSeq(
|
|
133
|
+
seqTrackOverlayctx: CanvasRenderingContext2D,
|
|
134
|
+
theme: Theme,
|
|
135
|
+
startPx: number,
|
|
136
|
+
sequenceRowHeight: number,
|
|
137
|
+
row: number | undefined,
|
|
138
|
+
widthPx: number,
|
|
139
|
+
) {
|
|
140
|
+
if (row !== undefined) {
|
|
141
|
+
seqTrackOverlayctx.fillStyle = theme.palette.action.focus
|
|
142
|
+
seqTrackOverlayctx.fillRect(
|
|
143
|
+
startPx,
|
|
144
|
+
sequenceRowHeight * row,
|
|
145
|
+
widthPx,
|
|
146
|
+
sequenceRowHeight,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function renderingModelFactory(
|
|
152
|
+
pluginManager: PluginManager,
|
|
153
|
+
configSchema: AnyConfigurationSchemaType,
|
|
154
|
+
) {
|
|
155
|
+
const BaseLinearApolloReferenceSequenceDisplay = baseModelFactory(
|
|
156
|
+
pluginManager,
|
|
157
|
+
configSchema,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return BaseLinearApolloReferenceSequenceDisplay.named(
|
|
161
|
+
'LinearApolloReferenceSequenceDisplayRendering',
|
|
162
|
+
)
|
|
163
|
+
.volatile(() => ({
|
|
164
|
+
seqTrackCanvas: null as HTMLCanvasElement | null,
|
|
165
|
+
seqTrackOverlayCanvas: null as HTMLCanvasElement | null,
|
|
166
|
+
theme: createTheme(),
|
|
167
|
+
}))
|
|
168
|
+
.actions((self) => ({
|
|
169
|
+
setSeqTrackCanvas(canvas: HTMLCanvasElement | null) {
|
|
170
|
+
self.seqTrackCanvas = canvas
|
|
171
|
+
},
|
|
172
|
+
setSeqTrackOverlayCanvas(canvas: HTMLCanvasElement | null) {
|
|
173
|
+
self.seqTrackOverlayCanvas = canvas
|
|
174
|
+
},
|
|
175
|
+
setTheme(theme: Theme) {
|
|
176
|
+
self.theme = theme
|
|
177
|
+
},
|
|
178
|
+
afterAttach() {
|
|
179
|
+
addDisposer(
|
|
180
|
+
self,
|
|
181
|
+
autorun(
|
|
182
|
+
() => {
|
|
183
|
+
const { theme } = self
|
|
184
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
const trnslWidthPx = 3 / self.lgv.bpPerPx
|
|
188
|
+
if (trnslWidthPx < 1) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
const seqTrackctx = self.seqTrackCanvas?.getContext('2d')
|
|
192
|
+
if (!seqTrackctx) {
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
seqTrackctx.clearRect(
|
|
197
|
+
0,
|
|
198
|
+
0,
|
|
199
|
+
self.lgv.dynamicBlocks.totalWidthPx,
|
|
200
|
+
self.height,
|
|
201
|
+
)
|
|
202
|
+
const frames =
|
|
203
|
+
self.lgv.bpPerPx <= 1
|
|
204
|
+
? [3, 2, 1, 0, 0, -1, -2, -3]
|
|
205
|
+
: [3, 2, 1, -1, -2, -3]
|
|
206
|
+
let height = 0
|
|
207
|
+
if (theme) {
|
|
208
|
+
for (const frame of frames) {
|
|
209
|
+
let frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
210
|
+
if (frameColor) {
|
|
211
|
+
let offsetPx = 0
|
|
212
|
+
if (self.highContrast) {
|
|
213
|
+
frameColor = 'white'
|
|
214
|
+
offsetPx = 1
|
|
215
|
+
// eslint-disable-next-line prefer-destructuring
|
|
216
|
+
seqTrackctx.fillStyle = theme.palette.grey[200]
|
|
217
|
+
seqTrackctx.fillRect(
|
|
218
|
+
0,
|
|
219
|
+
height,
|
|
220
|
+
self.lgv.dynamicBlocks.totalWidthPx,
|
|
221
|
+
self.sequenceRowHeight,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
seqTrackctx.fillStyle = frameColor
|
|
225
|
+
seqTrackctx.fillRect(
|
|
226
|
+
0 + offsetPx,
|
|
227
|
+
height + offsetPx,
|
|
228
|
+
self.lgv.dynamicBlocks.totalWidthPx - 2 * offsetPx,
|
|
229
|
+
self.sequenceRowHeight - 2 * offsetPx,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
height += self.sequenceRowHeight
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const [idx, region] of self.regions.entries()) {
|
|
237
|
+
const { apolloDataStore } =
|
|
238
|
+
self.session as unknown as ApolloSessionModel
|
|
239
|
+
const assembly = apolloDataStore.assemblies.get(
|
|
240
|
+
region.assemblyName,
|
|
241
|
+
)
|
|
242
|
+
const ref = assembly?.getByRefName(region.refName)
|
|
243
|
+
const seq = ref?.getSequence(region.start, region.end)
|
|
244
|
+
if (!seq) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
// disable because sequence is all ascii
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-spread
|
|
249
|
+
for (const [i, letter] of [...seq].entries()) {
|
|
250
|
+
const trnslXOffset =
|
|
251
|
+
(self.lgv.bpToPx({
|
|
252
|
+
refName: region.refName,
|
|
253
|
+
coord: region.start + i,
|
|
254
|
+
regionNumber: idx,
|
|
255
|
+
})?.offsetPx ?? 0) - self.lgv.offsetPx
|
|
256
|
+
const trnslStartPx = self.lgv.displayedRegions[idx].reversed
|
|
257
|
+
? trnslXOffset - trnslWidthPx
|
|
258
|
+
: trnslXOffset
|
|
259
|
+
|
|
260
|
+
// Draw translation forward
|
|
261
|
+
for (let j = 2; j >= 0; j--) {
|
|
262
|
+
if ((region.start + i) % 3 === j) {
|
|
263
|
+
drawTranslation(
|
|
264
|
+
seqTrackctx,
|
|
265
|
+
self.lgv.bpPerPx,
|
|
266
|
+
trnslStartPx,
|
|
267
|
+
self.sequenceRowHeight * (2 - j),
|
|
268
|
+
trnslWidthPx,
|
|
269
|
+
self.sequenceRowHeight,
|
|
270
|
+
seq,
|
|
271
|
+
i,
|
|
272
|
+
false,
|
|
273
|
+
self.showStartCodons,
|
|
274
|
+
self.showStopCodons,
|
|
275
|
+
self.highContrast,
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (self.lgv.bpPerPx <= 1) {
|
|
281
|
+
const xOffset =
|
|
282
|
+
(self.lgv.bpToPx({
|
|
283
|
+
refName: region.refName,
|
|
284
|
+
coord: region.start + i,
|
|
285
|
+
regionNumber: idx,
|
|
286
|
+
})?.offsetPx ?? 0) - self.lgv.offsetPx
|
|
287
|
+
const widthPx = 1 / self.lgv.bpPerPx
|
|
288
|
+
const startPx = self.lgv.displayedRegions[idx].reversed
|
|
289
|
+
? xOffset - widthPx
|
|
290
|
+
: xOffset
|
|
291
|
+
|
|
292
|
+
// Draw forward
|
|
293
|
+
seqTrackctx.beginPath()
|
|
294
|
+
seqTrackctx.fillStyle = colorCode(letter, self.theme)
|
|
295
|
+
seqTrackctx.rect(
|
|
296
|
+
startPx,
|
|
297
|
+
self.sequenceRowHeight * 3,
|
|
298
|
+
widthPx,
|
|
299
|
+
self.sequenceRowHeight,
|
|
300
|
+
)
|
|
301
|
+
seqTrackctx.fill()
|
|
302
|
+
if (self.lgv.bpPerPx <= 0.1) {
|
|
303
|
+
seqTrackctx.stroke()
|
|
304
|
+
drawLetter(
|
|
305
|
+
seqTrackctx,
|
|
306
|
+
startPx,
|
|
307
|
+
widthPx,
|
|
308
|
+
letter,
|
|
309
|
+
self.sequenceRowHeight * 3,
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Draw reverse
|
|
314
|
+
const revLetter = revcom(letter)
|
|
315
|
+
seqTrackctx.beginPath()
|
|
316
|
+
seqTrackctx.fillStyle = colorCode(revLetter, self.theme)
|
|
317
|
+
seqTrackctx.rect(
|
|
318
|
+
startPx,
|
|
319
|
+
self.sequenceRowHeight * 4,
|
|
320
|
+
widthPx,
|
|
321
|
+
self.sequenceRowHeight,
|
|
322
|
+
)
|
|
323
|
+
seqTrackctx.fill()
|
|
324
|
+
if (self.lgv.bpPerPx <= 0.1) {
|
|
325
|
+
seqTrackctx.stroke()
|
|
326
|
+
drawLetter(
|
|
327
|
+
seqTrackctx,
|
|
328
|
+
startPx,
|
|
329
|
+
widthPx,
|
|
330
|
+
revLetter,
|
|
331
|
+
self.sequenceRowHeight * 4,
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Draw translation reverse
|
|
337
|
+
for (let k = 0; k <= 2; k++) {
|
|
338
|
+
const rowOffset = self.lgv.bpPerPx <= 1 ? 5 : 3
|
|
339
|
+
if ((region.start + i) % 3 === k) {
|
|
340
|
+
drawTranslation(
|
|
341
|
+
seqTrackctx,
|
|
342
|
+
self.lgv.bpPerPx,
|
|
343
|
+
trnslStartPx,
|
|
344
|
+
self.sequenceRowHeight * (rowOffset + k),
|
|
345
|
+
trnslWidthPx,
|
|
346
|
+
self.sequenceRowHeight,
|
|
347
|
+
seq,
|
|
348
|
+
i,
|
|
349
|
+
true,
|
|
350
|
+
self.showStartCodons,
|
|
351
|
+
self.showStopCodons,
|
|
352
|
+
self.highContrast,
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{ name: 'LinearApolloReferenceSequenceDisplayRenderSequence' },
|
|
360
|
+
),
|
|
361
|
+
)
|
|
362
|
+
addDisposer(
|
|
363
|
+
self,
|
|
364
|
+
autorun(
|
|
365
|
+
() => {
|
|
366
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
const seqTrackOverlayctx =
|
|
370
|
+
self.seqTrackOverlayCanvas?.getContext('2d')
|
|
371
|
+
if (!seqTrackOverlayctx) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
seqTrackOverlayctx.clearRect(
|
|
376
|
+
0,
|
|
377
|
+
0,
|
|
378
|
+
self.lgv.dynamicBlocks.totalWidthPx,
|
|
379
|
+
self.height,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
const {
|
|
383
|
+
hoveredFeature,
|
|
384
|
+
lgv,
|
|
385
|
+
regions,
|
|
386
|
+
sequenceRowHeight,
|
|
387
|
+
session,
|
|
388
|
+
theme,
|
|
389
|
+
} = self
|
|
390
|
+
|
|
391
|
+
if (!hoveredFeature) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
const { feature } = hoveredFeature
|
|
395
|
+
|
|
396
|
+
const { featureTypeOntology } =
|
|
397
|
+
session.apolloDataStore.ontologyManager
|
|
398
|
+
if (!featureTypeOntology) {
|
|
399
|
+
throw new Error('featureTypeOntology is undefined')
|
|
400
|
+
}
|
|
401
|
+
for (const [idx, region] of regions.entries()) {
|
|
402
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
|
|
403
|
+
const parentFeature = feature.parent
|
|
404
|
+
if (!parentFeature) {
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
const cdsLocs = parentFeature.cdsLocations.find(
|
|
408
|
+
(loc) =>
|
|
409
|
+
feature.min === loc.at(0)?.min &&
|
|
410
|
+
feature.max === loc.at(-1)?.max,
|
|
411
|
+
)
|
|
412
|
+
if (!cdsLocs) {
|
|
413
|
+
continue
|
|
414
|
+
}
|
|
415
|
+
for (const dl of cdsLocs) {
|
|
416
|
+
const frame = getFrame(
|
|
417
|
+
dl.min,
|
|
418
|
+
dl.max,
|
|
419
|
+
feature.strand ?? 1,
|
|
420
|
+
dl.phase,
|
|
421
|
+
)
|
|
422
|
+
const row = getTranslationRow(frame, lgv.bpPerPx)
|
|
423
|
+
const offset =
|
|
424
|
+
(lgv.bpToPx({
|
|
425
|
+
refName: region.refName,
|
|
426
|
+
coord: dl.min,
|
|
427
|
+
regionNumber: idx,
|
|
428
|
+
})?.offsetPx ?? 0) - lgv.offsetPx
|
|
429
|
+
const widthPx = (dl.max - dl.min) / lgv.bpPerPx
|
|
430
|
+
const startPx = lgv.displayedRegions[idx].reversed
|
|
431
|
+
? offset - widthPx
|
|
432
|
+
: offset
|
|
433
|
+
|
|
434
|
+
highlightSeq(
|
|
435
|
+
seqTrackOverlayctx,
|
|
436
|
+
theme,
|
|
437
|
+
startPx,
|
|
438
|
+
sequenceRowHeight,
|
|
439
|
+
row,
|
|
440
|
+
widthPx,
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
const row = getSeqRow(feature.strand, lgv.bpPerPx)
|
|
445
|
+
const offset =
|
|
446
|
+
(lgv.bpToPx({
|
|
447
|
+
refName: region.refName,
|
|
448
|
+
coord: feature.min,
|
|
449
|
+
regionNumber: idx,
|
|
450
|
+
})?.offsetPx ?? 0) - lgv.offsetPx
|
|
451
|
+
const widthPx = feature.length / lgv.bpPerPx
|
|
452
|
+
const startPx = lgv.displayedRegions[idx].reversed
|
|
453
|
+
? offset - widthPx
|
|
454
|
+
: offset
|
|
455
|
+
|
|
456
|
+
highlightSeq(
|
|
457
|
+
seqTrackOverlayctx,
|
|
458
|
+
theme,
|
|
459
|
+
startPx,
|
|
460
|
+
sequenceRowHeight,
|
|
461
|
+
row,
|
|
462
|
+
widthPx,
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
{ name: 'LinearApolloDisplayRenderSeqHighlight' },
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
},
|
|
471
|
+
}))
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export type LinearApolloReferenceSequenceDisplayRenderingModel = ReturnType<
|
|
475
|
+
typeof renderingModelFactory
|
|
476
|
+
>
|
|
477
|
+
// eslint disable because of
|
|
478
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
479
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
480
|
+
export interface LinearApolloReferenceSequenceDisplayRendering
|
|
481
|
+
extends Instance<LinearApolloReferenceSequenceDisplayRenderingModel> {}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
+
import { type CheckResultI } from '@apollo-annotation/mst'
|
|
4
5
|
import { Menu, type MenuItem } from '@jbrowse/core/ui'
|
|
5
6
|
import {
|
|
6
7
|
type AbstractSessionModel,
|
|
7
8
|
doesIntersect2,
|
|
8
9
|
getContainingView,
|
|
10
|
+
getFrame,
|
|
9
11
|
} from '@jbrowse/core/util'
|
|
10
12
|
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
11
13
|
import ErrorIcon from '@mui/icons-material/Error'
|
|
12
|
-
import { Alert, Avatar, Tooltip, useTheme } from '@mui/material'
|
|
14
|
+
import { Alert, Avatar, Badge, Box, Tooltip, useTheme } from '@mui/material'
|
|
13
15
|
import { observer } from 'mobx-react'
|
|
14
16
|
import React, { useEffect, useState } from 'react'
|
|
15
|
-
import { makeStyles } from 'tss-react/mui'
|
|
16
17
|
|
|
18
|
+
import {
|
|
19
|
+
type Coord,
|
|
20
|
+
clusterResultByMessage,
|
|
21
|
+
useStyles,
|
|
22
|
+
} from '../../util/displayUtils'
|
|
17
23
|
import { type LinearApolloSixFrameDisplay as LinearApolloSixFrameDisplayI } from '../stateModel'
|
|
18
24
|
|
|
19
25
|
import { TrackLines } from './TrackLines'
|
|
@@ -21,31 +27,11 @@ import { TrackLines } from './TrackLines'
|
|
|
21
27
|
interface LinearApolloSixFrameDisplayProps {
|
|
22
28
|
model: LinearApolloSixFrameDisplayI
|
|
23
29
|
}
|
|
24
|
-
export type Coord = [number, number]
|
|
25
|
-
|
|
26
|
-
const useStyles = makeStyles()((theme) => ({
|
|
27
|
-
canvasContainer: {
|
|
28
|
-
position: 'relative',
|
|
29
|
-
left: 0,
|
|
30
|
-
},
|
|
31
|
-
canvas: {
|
|
32
|
-
position: 'absolute',
|
|
33
|
-
left: 0,
|
|
34
|
-
},
|
|
35
|
-
ellipses: {
|
|
36
|
-
textOverflow: 'ellipsis',
|
|
37
|
-
overflow: 'hidden',
|
|
38
|
-
},
|
|
39
|
-
avatar: {
|
|
40
|
-
position: 'absolute',
|
|
41
|
-
color: theme.palette.warning.light,
|
|
42
|
-
backgroundColor: theme.palette.warning.contrastText,
|
|
43
|
-
},
|
|
44
|
-
}))
|
|
45
30
|
|
|
46
31
|
export const LinearApolloSixFrameDisplay = observer(
|
|
47
32
|
function LinearApolloSixFrameDisplay(
|
|
48
33
|
props: LinearApolloSixFrameDisplayProps,
|
|
34
|
+
apolloDragging,
|
|
49
35
|
) {
|
|
50
36
|
const theme = useTheme()
|
|
51
37
|
const { model } = props
|
|
@@ -54,6 +40,8 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
54
40
|
contextMenuItems: getContextMenuItems,
|
|
55
41
|
cursor,
|
|
56
42
|
featuresHeight,
|
|
43
|
+
featureLabelSpacer,
|
|
44
|
+
geneTrackRowNums,
|
|
57
45
|
isShown,
|
|
58
46
|
onMouseDown,
|
|
59
47
|
onMouseLeave,
|
|
@@ -65,6 +53,7 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
65
53
|
setCollaboratorCanvas,
|
|
66
54
|
setOverlayCanvas,
|
|
67
55
|
setTheme,
|
|
56
|
+
showCheckResults,
|
|
68
57
|
} = model
|
|
69
58
|
const { classes } = useStyles()
|
|
70
59
|
const lgv = getContainingView(model) as unknown as LinearGenomeViewModel
|
|
@@ -95,12 +84,16 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
95
84
|
} else {
|
|
96
85
|
const coord: [number, number] = [event.clientX, event.clientY]
|
|
97
86
|
setContextCoord(coord)
|
|
98
|
-
setContextMenuItems(getContextMenuItems(
|
|
87
|
+
setContextMenuItems(getContextMenuItems(event))
|
|
99
88
|
}
|
|
100
89
|
}}
|
|
101
90
|
>
|
|
102
91
|
{message ? (
|
|
103
|
-
<Alert
|
|
92
|
+
<Alert
|
|
93
|
+
severity="warning"
|
|
94
|
+
classes={{ message: classes.ellipses }}
|
|
95
|
+
slotProps={{ root: { className: classes.center } }}
|
|
96
|
+
>
|
|
104
97
|
<Tooltip title={message}>
|
|
105
98
|
<div>{message}</div>
|
|
106
99
|
</Tooltip>
|
|
@@ -152,9 +145,12 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
152
145
|
data-testid="overlayCanvas"
|
|
153
146
|
/>
|
|
154
147
|
{lgv.displayedRegions.flatMap((region, idx) => {
|
|
148
|
+
const widthBp = lgv.bpPerPx * apolloRowHeight
|
|
155
149
|
const assembly = assemblyManager.get(region.assemblyName)
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
if (showCheckResults) {
|
|
151
|
+
const filteredCheckResults = [
|
|
152
|
+
...session.apolloDataStore.checkResults.values(),
|
|
153
|
+
].filter(
|
|
158
154
|
(checkResult) =>
|
|
159
155
|
assembly?.isValidRefName(checkResult.refSeq) &&
|
|
160
156
|
assembly.getCanonicalRefName(checkResult.refSeq) ===
|
|
@@ -166,33 +162,92 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
166
162
|
checkResult.end,
|
|
167
163
|
),
|
|
168
164
|
)
|
|
169
|
-
|
|
165
|
+
const checkResults = clusterResultByMessage<CheckResultI>(
|
|
166
|
+
filteredCheckResults,
|
|
167
|
+
widthBp,
|
|
168
|
+
true,
|
|
169
|
+
)
|
|
170
|
+
return checkResults.map((checkResult) => {
|
|
170
171
|
const left =
|
|
171
172
|
(lgv.bpToPx({
|
|
172
173
|
refName: region.refName,
|
|
173
174
|
coord: checkResult.start,
|
|
174
175
|
regionNumber: idx,
|
|
175
176
|
})?.offsetPx ?? 0) - lgv.offsetPx
|
|
176
|
-
const [feature] = checkResult.
|
|
177
|
+
const [feature] = checkResult.featureIds
|
|
177
178
|
if (!feature || !feature.parent?.looksLikeGene) {
|
|
178
179
|
return null
|
|
179
180
|
}
|
|
180
|
-
|
|
181
|
+
|
|
182
|
+
let row
|
|
183
|
+
for (const loc of feature.cdsLocations) {
|
|
184
|
+
for (const cds of loc) {
|
|
185
|
+
let rowNum: number = getFrame(
|
|
186
|
+
cds.min,
|
|
187
|
+
cds.max,
|
|
188
|
+
feature.strand ?? 1,
|
|
189
|
+
cds.phase,
|
|
190
|
+
)
|
|
191
|
+
rowNum = featureLabelSpacer(
|
|
192
|
+
rowNum < 0 ? -1 * rowNum + 5 : rowNum,
|
|
193
|
+
)
|
|
194
|
+
if (
|
|
195
|
+
checkResult.start >= cds.min &&
|
|
196
|
+
checkResult.start <= cds.max
|
|
197
|
+
) {
|
|
198
|
+
row = rowNum - 1
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (row === undefined) {
|
|
204
|
+
const rowNum =
|
|
205
|
+
feature.strand == 1
|
|
206
|
+
? geneTrackRowNums[0]
|
|
207
|
+
: geneTrackRowNums[1]
|
|
208
|
+
row = rowNum - 1
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const top = row * apolloRowHeight
|
|
181
212
|
const height = apolloRowHeight
|
|
182
213
|
return (
|
|
183
214
|
<Tooltip
|
|
184
215
|
key={checkResult._id}
|
|
185
216
|
title={checkResult.message}
|
|
186
217
|
>
|
|
187
|
-
<
|
|
188
|
-
className={classes.
|
|
189
|
-
style={{
|
|
218
|
+
<Box
|
|
219
|
+
className={classes.box}
|
|
220
|
+
style={{
|
|
221
|
+
top,
|
|
222
|
+
left,
|
|
223
|
+
height,
|
|
224
|
+
width: height,
|
|
225
|
+
pointerEvents: apolloDragging ? 'none' : 'auto',
|
|
226
|
+
}}
|
|
190
227
|
>
|
|
191
|
-
<
|
|
192
|
-
|
|
228
|
+
<Badge
|
|
229
|
+
className={classes.badge}
|
|
230
|
+
badgeContent={checkResult.count}
|
|
231
|
+
color="primary"
|
|
232
|
+
overlap="circular"
|
|
233
|
+
anchorOrigin={{
|
|
234
|
+
vertical: 'bottom',
|
|
235
|
+
horizontal: 'right',
|
|
236
|
+
}}
|
|
237
|
+
invisible={checkResult.count <= 1}
|
|
238
|
+
>
|
|
239
|
+
<Avatar className={classes.avatar}>
|
|
240
|
+
<ErrorIcon
|
|
241
|
+
data-testid={`ErrorIcon-${checkResult.start}`}
|
|
242
|
+
/>
|
|
243
|
+
</Avatar>
|
|
244
|
+
</Badge>
|
|
245
|
+
</Box>
|
|
193
246
|
</Tooltip>
|
|
194
247
|
)
|
|
195
248
|
})
|
|
249
|
+
}
|
|
250
|
+
return null
|
|
196
251
|
})}
|
|
197
252
|
<Menu
|
|
198
253
|
open={contextMenuItems.length > 0}
|
|
@@ -203,9 +258,11 @@ export const LinearApolloSixFrameDisplay = observer(
|
|
|
203
258
|
onClose={() => {
|
|
204
259
|
setContextMenuItems([])
|
|
205
260
|
}}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
slotProps={{
|
|
262
|
+
transition: {
|
|
263
|
+
onExit: () => {
|
|
264
|
+
setContextMenuItems([])
|
|
265
|
+
},
|
|
209
266
|
},
|
|
210
267
|
}}
|
|
211
268
|
anchorReference="anchorPosition"
|