@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.7
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 +2679 -850
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2676 -847
- 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 +5194 -1258
- 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 +4 -4
- package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
- package/src/ChangeManager.ts +10 -6
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +12 -20
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +929 -175
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -1
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +48 -60
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +244 -51
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +46 -1
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +9 -1
- package/src/LinearApolloDisplay/stateModel/base.ts +29 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +51 -35
- package/src/LinearApolloDisplay/stateModel/rendering.ts +2 -1
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +243 -124
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -1
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +19 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +53 -34
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +4 -2
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +4 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +3 -6
- package/src/components/AddFeature.tsx +14 -15
- package/src/components/CopyFeature.tsx +2 -4
- package/src/components/CreateApolloAnnotation.tsx +334 -151
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +185 -0
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/index.ts +3 -0
- package/src/config.ts +5 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/session/session.ts +26 -13
- package/src/util/annotationFeatureUtils.ts +65 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/glyphUtils.ts +49 -0
- package/src/util/index.ts +2 -0
- package/src/util/mouseEventsUtils.ts +113 -0
|
@@ -5,15 +5,17 @@ import {
|
|
|
5
5
|
import { type MenuItem } from '@jbrowse/core/ui'
|
|
6
6
|
import {
|
|
7
7
|
type AbstractSessionModel,
|
|
8
|
-
type SessionWithWidgets,
|
|
9
8
|
getFrame,
|
|
10
9
|
intersection2,
|
|
11
|
-
|
|
10
|
+
measureText,
|
|
12
11
|
} from '@jbrowse/core/util'
|
|
13
12
|
import { alpha } from '@mui/material'
|
|
14
13
|
import equal from 'fast-deep-equal/es6'
|
|
14
|
+
import { getSnapshot } from 'mobx-state-tree'
|
|
15
15
|
|
|
16
16
|
import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
|
|
17
|
+
import { FilterTranscripts } from '../../components/FilterTranscripts'
|
|
18
|
+
import { getMinAndMaxPx, getOverlappingEdge } from '../../util'
|
|
17
19
|
import { type LinearApolloSixFrameDisplay } from '../stateModel'
|
|
18
20
|
import {
|
|
19
21
|
type LinearApolloSixFrameDisplayMouseEvents,
|
|
@@ -30,7 +32,10 @@ let forwardFillLight: CanvasPattern | null = null
|
|
|
30
32
|
let backwardFillLight: CanvasPattern | null = null
|
|
31
33
|
let forwardFillDark: CanvasPattern | null = null
|
|
32
34
|
let backwardFillDark: CanvasPattern | null = null
|
|
33
|
-
|
|
35
|
+
const canvas = globalThis.document.createElement('canvas')
|
|
36
|
+
// @ts-expect-error getContext is undefined in the web worker
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
38
|
+
if (canvas?.getContext) {
|
|
34
39
|
for (const direction of ['forward', 'backward']) {
|
|
35
40
|
for (const themeMode of ['light', 'dark']) {
|
|
36
41
|
const canvas = document.createElement('canvas')
|
|
@@ -83,6 +88,37 @@ function deepSetHas<T>(set: Set<T>, item: T): boolean {
|
|
|
83
88
|
return false
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
interface Label {
|
|
92
|
+
x: number
|
|
93
|
+
y: number
|
|
94
|
+
h: number
|
|
95
|
+
text: string | undefined
|
|
96
|
+
color: string
|
|
97
|
+
isSelected: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function drawTextLabels(
|
|
101
|
+
ctx: CanvasRenderingContext2D,
|
|
102
|
+
labelArray: Label[],
|
|
103
|
+
font = '10px sans-serif',
|
|
104
|
+
) {
|
|
105
|
+
for (let i = labelArray.length - 1; i >= 0; --i) {
|
|
106
|
+
const label = labelArray[i]
|
|
107
|
+
ctx.fillStyle = label.color
|
|
108
|
+
const labelRowX = Math.max(label.x + 1, 0)
|
|
109
|
+
const labelRowY = label.y + label.h
|
|
110
|
+
const textWidth = measureText(label.text, 10)
|
|
111
|
+
if (label.isSelected) {
|
|
112
|
+
ctx.clearRect(labelRowX - 5, labelRowY, textWidth + 10, label.h)
|
|
113
|
+
ctx.font = 'bold '.concat(font)
|
|
114
|
+
}
|
|
115
|
+
if (label.text) {
|
|
116
|
+
ctx.fillText(label.text, labelRowX, labelRowY + 11, textWidth)
|
|
117
|
+
ctx.font = font
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
86
122
|
function draw(
|
|
87
123
|
ctx: CanvasRenderingContext2D,
|
|
88
124
|
topLevelFeature: AnnotationFeature,
|
|
@@ -90,14 +126,25 @@ function draw(
|
|
|
90
126
|
stateModel: LinearApolloSixFrameDisplayRendering,
|
|
91
127
|
displayedRegionIndex: number,
|
|
92
128
|
): void {
|
|
93
|
-
const {
|
|
129
|
+
const {
|
|
130
|
+
apolloRowHeight,
|
|
131
|
+
lgv,
|
|
132
|
+
session,
|
|
133
|
+
theme,
|
|
134
|
+
highestRow,
|
|
135
|
+
filteredTranscripts,
|
|
136
|
+
showFeatureLabels,
|
|
137
|
+
} = stateModel
|
|
94
138
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
95
139
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
96
140
|
const { refName, reversed } = displayedRegion
|
|
97
141
|
const rowHeight = apolloRowHeight
|
|
98
|
-
const exonHeight =
|
|
99
|
-
const cdsHeight =
|
|
100
|
-
const
|
|
142
|
+
const exonHeight = rowHeight
|
|
143
|
+
const cdsHeight = rowHeight
|
|
144
|
+
const topLevelFeatureHeight = rowHeight
|
|
145
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
146
|
+
const textColor = theme?.palette.text.primary ?? 'black'
|
|
147
|
+
const { attributes, children, min, strand } = topLevelFeature
|
|
101
148
|
if (!children) {
|
|
102
149
|
return
|
|
103
150
|
}
|
|
@@ -107,6 +154,7 @@ function draw(
|
|
|
107
154
|
if (!featureTypeOntology) {
|
|
108
155
|
throw new Error('featureTypeOntology is undefined')
|
|
109
156
|
}
|
|
157
|
+
const labelArray: Label[] = []
|
|
110
158
|
|
|
111
159
|
// Draw background for gene
|
|
112
160
|
const topLevelFeatureMinX =
|
|
@@ -119,10 +167,8 @@ function draw(
|
|
|
119
167
|
const topLevelFeatureStartPx = reversed
|
|
120
168
|
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
121
169
|
: topLevelFeatureMinX
|
|
122
|
-
const topLevelRow = strand == 1 ? 3 : 4
|
|
170
|
+
const topLevelRow = (strand == 1 ? 3 : 4) * featureLabelSpacer
|
|
123
171
|
const topLevelFeatureTop = topLevelRow * rowHeight
|
|
124
|
-
const topLevelFeatureHeight = Math.round(0.7 * rowHeight)
|
|
125
|
-
|
|
126
172
|
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
127
173
|
ctx.fillRect(
|
|
128
174
|
topLevelFeatureStartPx,
|
|
@@ -131,10 +177,9 @@ function draw(
|
|
|
131
177
|
topLevelFeatureHeight,
|
|
132
178
|
)
|
|
133
179
|
|
|
134
|
-
ctx.fillStyle =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
: alpha(theme?.palette.background.paper ?? '#ffffff', 0.7)
|
|
180
|
+
ctx.fillStyle = isSelectedFeature(topLevelFeature, apolloSelectedFeature)
|
|
181
|
+
? alpha('rgb(0,0,0)', 0.7)
|
|
182
|
+
: alpha(theme?.palette.background.paper ?? '#ffffff', 0.7)
|
|
138
183
|
ctx.fillRect(
|
|
139
184
|
topLevelFeatureStartPx + 1,
|
|
140
185
|
topLevelFeatureTop + 1,
|
|
@@ -142,6 +187,21 @@ function draw(
|
|
|
142
187
|
topLevelFeatureHeight - 2,
|
|
143
188
|
)
|
|
144
189
|
|
|
190
|
+
const isSelected = isSelectedFeature(topLevelFeature, apolloSelectedFeature)
|
|
191
|
+
const label: Label = {
|
|
192
|
+
x: topLevelFeatureStartPx,
|
|
193
|
+
y: topLevelFeatureTop,
|
|
194
|
+
h: topLevelFeatureHeight,
|
|
195
|
+
text: attributes.get('gff_id')?.toString(),
|
|
196
|
+
color: textColor,
|
|
197
|
+
isSelected,
|
|
198
|
+
}
|
|
199
|
+
if (isSelected) {
|
|
200
|
+
labelArray.unshift(label)
|
|
201
|
+
} else {
|
|
202
|
+
labelArray.push(label)
|
|
203
|
+
}
|
|
204
|
+
|
|
145
205
|
const forwardFill =
|
|
146
206
|
theme?.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
147
207
|
const backwardFill =
|
|
@@ -184,10 +244,16 @@ function draw(
|
|
|
184
244
|
) {
|
|
185
245
|
continue
|
|
186
246
|
}
|
|
187
|
-
const { children: childrenOfmRNA, cdsLocations
|
|
247
|
+
const { children: childrenOfmRNA, cdsLocations } = child
|
|
188
248
|
if (!childrenOfmRNA) {
|
|
189
249
|
continue
|
|
190
250
|
}
|
|
251
|
+
const childID: string | undefined = child.attributes
|
|
252
|
+
.get('gff_id')
|
|
253
|
+
?.toString()
|
|
254
|
+
if (childID && filteredTranscripts.includes(childID)) {
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
191
257
|
for (const [, exon] of childrenOfmRNA) {
|
|
192
258
|
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
193
259
|
continue
|
|
@@ -203,14 +269,12 @@ function draw(
|
|
|
203
269
|
|
|
204
270
|
const exonTop =
|
|
205
271
|
topLevelFeatureTop + (topLevelFeatureHeight - exonHeight) / 2
|
|
272
|
+
const isSelected = isSelectedFeature(exon, apolloSelectedFeature)
|
|
206
273
|
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
207
274
|
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
208
275
|
if (widthPx > 2) {
|
|
209
276
|
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
210
|
-
ctx.fillStyle =
|
|
211
|
-
apolloSelectedFeature && exon._id === apolloSelectedFeature._id
|
|
212
|
-
? 'rgb(0,0,0)'
|
|
213
|
-
: alpha('#f5f500', 0.6)
|
|
277
|
+
ctx.fillStyle = isSelected ? 'rgb(0,0,0)' : alpha('#f5f500', 0.6)
|
|
214
278
|
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
215
279
|
if (topFill && bottomFill) {
|
|
216
280
|
ctx.fillStyle = topFill
|
|
@@ -228,9 +292,26 @@ function draw(
|
|
|
228
292
|
(exonHeight - 2) / 2,
|
|
229
293
|
)
|
|
230
294
|
}
|
|
295
|
+
|
|
296
|
+
const label: Label = {
|
|
297
|
+
x: startPx,
|
|
298
|
+
y: exonTop,
|
|
299
|
+
h: exonHeight,
|
|
300
|
+
text: exon.attributes.get('gff_id')?.toString(),
|
|
301
|
+
color: textColor,
|
|
302
|
+
isSelected,
|
|
303
|
+
}
|
|
304
|
+
if (isSelected) {
|
|
305
|
+
labelArray.unshift(label)
|
|
306
|
+
} else {
|
|
307
|
+
labelArray.push(label)
|
|
308
|
+
}
|
|
231
309
|
}
|
|
232
310
|
}
|
|
233
311
|
|
|
312
|
+
const isSelected = isSelectedFeature(child, apolloSelectedFeature?.parent)
|
|
313
|
+
let cdsStartPx = 0
|
|
314
|
+
let cdsTop = 0
|
|
234
315
|
for (const cdsRow of cdsLocations) {
|
|
235
316
|
let prevCDSTop = 0
|
|
236
317
|
let prevCDSEndPx = 0
|
|
@@ -238,8 +319,8 @@ function draw(
|
|
|
238
319
|
for (const cds of cdsRow.sort((a, b) => a.max - b.max)) {
|
|
239
320
|
if (
|
|
240
321
|
(apolloSelectedFeature &&
|
|
241
|
-
|
|
242
|
-
|
|
322
|
+
isSelected &&
|
|
323
|
+
featureTypeOntology.isTypeOf(apolloSelectedFeature.type, 'CDS')) ||
|
|
243
324
|
!deepSetHas(renderedCDS, cds)
|
|
244
325
|
) {
|
|
245
326
|
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
@@ -249,12 +330,12 @@ function draw(
|
|
|
249
330
|
coord: cds.min,
|
|
250
331
|
regionNumber: displayedRegionIndex,
|
|
251
332
|
})?.offsetPx ?? 0) - offsetPx
|
|
252
|
-
|
|
333
|
+
cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
253
334
|
ctx.fillStyle = theme?.palette.text.primary ?? 'black'
|
|
254
335
|
const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
|
|
255
|
-
const frameAdjust =
|
|
256
|
-
|
|
257
|
-
|
|
336
|
+
const frameAdjust =
|
|
337
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
338
|
+
cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
258
339
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
259
340
|
if (cdsWidthPx > 2) {
|
|
260
341
|
ctx.clearRect(
|
|
@@ -269,8 +350,8 @@ function draw(
|
|
|
269
350
|
ctx.fillStyle = cdsColorCode
|
|
270
351
|
ctx.fillStyle =
|
|
271
352
|
apolloSelectedFeature &&
|
|
272
|
-
|
|
273
|
-
|
|
353
|
+
isSelected &&
|
|
354
|
+
featureTypeOntology.isTypeOf(apolloSelectedFeature.type, 'CDS')
|
|
274
355
|
? 'rgb(0,0,0)'
|
|
275
356
|
: cdsColorCode
|
|
276
357
|
ctx.fillRect(
|
|
@@ -286,7 +367,9 @@ function draw(
|
|
|
286
367
|
const midPoint: [number, number] = [
|
|
287
368
|
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
288
369
|
Math.max(
|
|
289
|
-
frame < 0
|
|
370
|
+
frame < 0
|
|
371
|
+
? rowHeight * featureLabelSpacer * highestRow + 1
|
|
372
|
+
: 1, // Avoid render ceiling
|
|
290
373
|
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
291
374
|
),
|
|
292
375
|
]
|
|
@@ -324,6 +407,22 @@ function draw(
|
|
|
324
407
|
}
|
|
325
408
|
}
|
|
326
409
|
}
|
|
410
|
+
const label: Label = {
|
|
411
|
+
x: cdsStartPx,
|
|
412
|
+
y: cdsTop,
|
|
413
|
+
h: cdsHeight,
|
|
414
|
+
text: child.attributes.get('gff_id')?.toString(),
|
|
415
|
+
color: textColor,
|
|
416
|
+
isSelected,
|
|
417
|
+
}
|
|
418
|
+
if (isSelected) {
|
|
419
|
+
labelArray.unshift(label)
|
|
420
|
+
} else {
|
|
421
|
+
labelArray.push(label)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (showFeatureLabels) {
|
|
425
|
+
drawTextLabels(ctx, labelArray)
|
|
327
426
|
}
|
|
328
427
|
}
|
|
329
428
|
|
|
@@ -360,7 +459,15 @@ function drawHover(
|
|
|
360
459
|
stateModel: LinearApolloSixFrameDisplay,
|
|
361
460
|
ctx: CanvasRenderingContext2D,
|
|
362
461
|
) {
|
|
363
|
-
const {
|
|
462
|
+
const {
|
|
463
|
+
apolloHover,
|
|
464
|
+
apolloRowHeight,
|
|
465
|
+
filteredTranscripts,
|
|
466
|
+
lgv,
|
|
467
|
+
highestRow,
|
|
468
|
+
session,
|
|
469
|
+
showFeatureLabels,
|
|
470
|
+
} = stateModel
|
|
364
471
|
if (!apolloHover) {
|
|
365
472
|
return
|
|
366
473
|
}
|
|
@@ -373,6 +480,12 @@ function drawHover(
|
|
|
373
480
|
if (!featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
374
481
|
return
|
|
375
482
|
}
|
|
483
|
+
const featureID: string | undefined = feature.attributes
|
|
484
|
+
.get('gff_id')
|
|
485
|
+
?.toString()
|
|
486
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
376
489
|
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
377
490
|
if (!position) {
|
|
378
491
|
return
|
|
@@ -382,7 +495,8 @@ function drawHover(
|
|
|
382
495
|
const displayedRegion = displayedRegions[layoutIndex]
|
|
383
496
|
const { refName, reversed } = displayedRegion
|
|
384
497
|
const rowHeight = apolloRowHeight
|
|
385
|
-
const cdsHeight =
|
|
498
|
+
const cdsHeight = rowHeight
|
|
499
|
+
const featureLabelSpacer = showFeatureLabels ? 2 : 1
|
|
386
500
|
const { cdsLocations, strand } = feature
|
|
387
501
|
for (const cdsRow of cdsLocations) {
|
|
388
502
|
let prevCDSTop = 0
|
|
@@ -399,9 +513,9 @@ function drawHover(
|
|
|
399
513
|
})?.offsetPx ?? 0) - offsetPx
|
|
400
514
|
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
401
515
|
const frame = getFrame(cds.min, cds.max, strand ?? 1, cds.phase)
|
|
402
|
-
const frameAdjust =
|
|
403
|
-
|
|
404
|
-
|
|
516
|
+
const frameAdjust =
|
|
517
|
+
(frame < 0 ? -1 * frame + 5 : frame) * featureLabelSpacer
|
|
518
|
+
const cdsTop = (frameAdjust - featureLabelSpacer) * rowHeight
|
|
405
519
|
ctx.fillStyle = 'rgba(255,0,0,0.6)'
|
|
406
520
|
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
407
521
|
|
|
@@ -410,7 +524,7 @@ function drawHover(
|
|
|
410
524
|
const midPoint: [number, number] = [
|
|
411
525
|
(cdsStartPx - prevCDSEndPx) / 2 + prevCDSEndPx,
|
|
412
526
|
Math.max(
|
|
413
|
-
frame < 0 ? rowHeight * highestRow + 1 : 1, // Avoid render ceiling
|
|
527
|
+
frame < 0 ? rowHeight * featureLabelSpacer * highestRow + 1 : 1, // Avoid render ceiling
|
|
414
528
|
Math.min(prevCDSTop, cdsTop) - rowHeight / 2,
|
|
415
529
|
),
|
|
416
530
|
]
|
|
@@ -453,6 +567,7 @@ function onMouseDown(
|
|
|
453
567
|
currentMousePosition,
|
|
454
568
|
draggableFeature.feature,
|
|
455
569
|
draggableFeature.edge,
|
|
570
|
+
true,
|
|
456
571
|
)
|
|
457
572
|
}
|
|
458
573
|
}
|
|
@@ -490,33 +605,53 @@ function onMouseUp(
|
|
|
490
605
|
const { session } = stateModel
|
|
491
606
|
const { apolloDataStore } = session
|
|
492
607
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
493
|
-
if (featureAndGlyphUnderMouse
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
608
|
+
if (!featureAndGlyphUnderMouse) {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
const { feature } = featureAndGlyphUnderMouse
|
|
612
|
+
stateModel.setSelectedFeature(feature)
|
|
613
|
+
if (!featureTypeOntology) {
|
|
614
|
+
throw new Error('featureTypeOntology is undefined')
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let containsCDSOrExon = false
|
|
618
|
+
for (const [, child] of feature.children ?? []) {
|
|
619
|
+
if (
|
|
620
|
+
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
621
|
+
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
622
|
+
) {
|
|
623
|
+
containsCDSOrExon = true
|
|
624
|
+
break
|
|
507
625
|
}
|
|
508
|
-
}
|
|
509
|
-
|
|
626
|
+
}
|
|
627
|
+
if (
|
|
628
|
+
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
629
|
+
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
630
|
+
containsCDSOrExon
|
|
631
|
+
) {
|
|
632
|
+
stateModel.showFeatureDetailsWidget(feature, [
|
|
633
|
+
'ApolloTranscriptDetails',
|
|
634
|
+
'apolloTranscriptDetails',
|
|
635
|
+
])
|
|
636
|
+
} else {
|
|
637
|
+
stateModel.showFeatureDetailsWidget(feature)
|
|
510
638
|
}
|
|
511
639
|
}
|
|
512
640
|
|
|
641
|
+
export function isSelectedFeature(
|
|
642
|
+
feature: AnnotationFeature,
|
|
643
|
+
selectedFeature: AnnotationFeature | undefined,
|
|
644
|
+
) {
|
|
645
|
+
return Boolean(selectedFeature && feature._id === selectedFeature._id)
|
|
646
|
+
}
|
|
647
|
+
|
|
513
648
|
function getDraggableFeatureInfo(
|
|
514
649
|
mousePosition: MousePosition,
|
|
515
650
|
cds: TranscriptPartCoding | null,
|
|
516
651
|
feature: AnnotationFeature,
|
|
517
652
|
stateModel: LinearApolloSixFrameDisplay,
|
|
518
653
|
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
519
|
-
const { session } = stateModel
|
|
654
|
+
const { filteredTranscripts, session } = stateModel
|
|
520
655
|
const { apolloDataStore } = session
|
|
521
656
|
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
522
657
|
if (!featureTypeOntology) {
|
|
@@ -526,63 +661,57 @@ function getDraggableFeatureInfo(
|
|
|
526
661
|
if (cds === null) {
|
|
527
662
|
return
|
|
528
663
|
}
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const minPxInfo = lgv.bpToPx({ refName, coord: cds.min, regionNumber })
|
|
534
|
-
const maxPxInfo = lgv.bpToPx({ refName, coord: cds.max, regionNumber })
|
|
535
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
const minPx = minPxInfo.offsetPx - offsetPx
|
|
539
|
-
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
540
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
664
|
+
const featureID: string | undefined = feature.attributes
|
|
665
|
+
.get('gff_id')
|
|
666
|
+
?.toString()
|
|
667
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
541
668
|
return
|
|
542
669
|
}
|
|
670
|
+
const { bp, refName, regionNumber, x } = mousePosition
|
|
671
|
+
const { lgv } = stateModel
|
|
543
672
|
if (isTranscript) {
|
|
544
673
|
const transcript = feature
|
|
545
674
|
if (!transcript.children) {
|
|
546
675
|
return
|
|
547
676
|
}
|
|
548
677
|
const exonChildren: AnnotationFeature[] = []
|
|
678
|
+
const cdsChildren: AnnotationFeature[] = []
|
|
549
679
|
for (const child of transcript.children.values()) {
|
|
550
680
|
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
681
|
+
const childIsCDS = featureTypeOntology.isTypeOf(child.type, 'CDS')
|
|
551
682
|
if (childIsExon) {
|
|
552
683
|
exonChildren.push(child)
|
|
684
|
+
} else if (childIsCDS) {
|
|
685
|
+
cdsChildren.push(child)
|
|
553
686
|
}
|
|
554
687
|
}
|
|
555
|
-
|
|
556
688
|
const overlappingExon = exonChildren.find((child) => {
|
|
557
689
|
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
558
690
|
return start !== undefined && end !== undefined
|
|
559
691
|
})
|
|
560
|
-
if (
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
refName,
|
|
570
|
-
coord: overlappingExon.max,
|
|
571
|
-
regionNumber,
|
|
572
|
-
})
|
|
573
|
-
if (minPxInfo === undefined || maxPxInfo === undefined) {
|
|
574
|
-
return
|
|
575
|
-
}
|
|
576
|
-
const minPx = minPxInfo.offsetPx - offsetPx
|
|
577
|
-
const maxPx = maxPxInfo.offsetPx - offsetPx
|
|
578
|
-
if (Math.abs(maxPx - minPx) < 8) {
|
|
579
|
-
return
|
|
580
|
-
}
|
|
581
|
-
if (Math.abs(minPx - x) < 4) {
|
|
582
|
-
return { feature: overlappingExon, edge: 'min' }
|
|
692
|
+
if (overlappingExon) {
|
|
693
|
+
// We are on an exon, are we on the edge of it?
|
|
694
|
+
const minMax = getMinAndMaxPx(overlappingExon, refName, regionNumber, lgv)
|
|
695
|
+
if (minMax) {
|
|
696
|
+
const overlappingEdge = getOverlappingEdge(overlappingExon, x, minMax)
|
|
697
|
+
if (overlappingEdge) {
|
|
698
|
+
return overlappingEdge
|
|
699
|
+
}
|
|
700
|
+
}
|
|
583
701
|
}
|
|
584
|
-
if
|
|
585
|
-
|
|
702
|
+
// End of special cases, let's see if we're on the edge of this CDS or exon
|
|
703
|
+
const minMax = getMinAndMaxPx(cds, refName, regionNumber, lgv)
|
|
704
|
+
if (minMax) {
|
|
705
|
+
const overlappingCDS = cdsChildren.find((child) => {
|
|
706
|
+
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
|
|
707
|
+
return start !== undefined && end !== undefined
|
|
708
|
+
})
|
|
709
|
+
if (overlappingCDS) {
|
|
710
|
+
const overlappingEdge = getOverlappingEdge(overlappingCDS, x, minMax)
|
|
711
|
+
if (overlappingEdge) {
|
|
712
|
+
return overlappingEdge
|
|
713
|
+
}
|
|
714
|
+
}
|
|
586
715
|
}
|
|
587
716
|
}
|
|
588
717
|
return
|
|
@@ -592,7 +721,8 @@ function drawTooltip(
|
|
|
592
721
|
display: LinearApolloSixFrameDisplayMouseEvents,
|
|
593
722
|
context: CanvasRenderingContext2D,
|
|
594
723
|
): void {
|
|
595
|
-
const { apolloHover, apolloRowHeight, lgv, theme } =
|
|
724
|
+
const { apolloHover, apolloRowHeight, filteredTranscripts, lgv, theme } =
|
|
725
|
+
display
|
|
596
726
|
if (!apolloHover) {
|
|
597
727
|
return
|
|
598
728
|
}
|
|
@@ -604,6 +734,12 @@ function drawTooltip(
|
|
|
604
734
|
if (!position) {
|
|
605
735
|
return
|
|
606
736
|
}
|
|
737
|
+
const featureID: string | undefined = feature.attributes
|
|
738
|
+
.get('gff_id')
|
|
739
|
+
?.toString()
|
|
740
|
+
if (featureID && filteredTranscripts.includes(featureID)) {
|
|
741
|
+
return
|
|
742
|
+
}
|
|
607
743
|
const { layoutIndex } = position
|
|
608
744
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
609
745
|
const displayedRegion = displayedRegions[layoutIndex]
|
|
@@ -675,6 +811,7 @@ function getContextMenuItems(
|
|
|
675
811
|
apolloHover,
|
|
676
812
|
apolloInternetAccount: internetAccount,
|
|
677
813
|
changeManager,
|
|
814
|
+
filteredTranscripts,
|
|
678
815
|
regions,
|
|
679
816
|
selectedFeature,
|
|
680
817
|
session,
|
|
@@ -756,48 +893,30 @@ function getContextMenuItems(
|
|
|
756
893
|
)
|
|
757
894
|
},
|
|
758
895
|
},
|
|
759
|
-
{
|
|
760
|
-
label: 'Edit feature details',
|
|
761
|
-
onClick: () => {
|
|
762
|
-
const apolloFeatureWidget = (
|
|
763
|
-
session as unknown as SessionWithWidgets
|
|
764
|
-
).addWidget(
|
|
765
|
-
'ApolloFeatureDetailsWidget',
|
|
766
|
-
'apolloFeatureDetailsWidget',
|
|
767
|
-
{
|
|
768
|
-
feature: sourceFeature,
|
|
769
|
-
assembly: currentAssemblyId,
|
|
770
|
-
refName: region.refName,
|
|
771
|
-
},
|
|
772
|
-
)
|
|
773
|
-
;(session as unknown as SessionWithWidgets).showWidget(
|
|
774
|
-
apolloFeatureWidget,
|
|
775
|
-
)
|
|
776
|
-
},
|
|
777
|
-
},
|
|
778
896
|
)
|
|
779
897
|
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
780
898
|
if (!featureTypeOntology) {
|
|
781
899
|
throw new Error('featureTypeOntology is undefined')
|
|
782
900
|
}
|
|
783
|
-
if (
|
|
784
|
-
featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') &&
|
|
785
|
-
isSessionModelWithWidgets(session)
|
|
786
|
-
) {
|
|
901
|
+
if (featureTypeOntology.isTypeOf(sourceFeature.type, 'gene')) {
|
|
787
902
|
menuItems.push({
|
|
788
|
-
label: '
|
|
903
|
+
label: 'Filter alternate transcripts',
|
|
789
904
|
onClick: () => {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
905
|
+
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
906
|
+
(doneCallback) => [
|
|
907
|
+
FilterTranscripts,
|
|
908
|
+
{
|
|
909
|
+
handleClose: () => {
|
|
910
|
+
doneCallback()
|
|
911
|
+
},
|
|
912
|
+
sourceFeature,
|
|
913
|
+
filteredTranscripts: getSnapshot(filteredTranscripts),
|
|
914
|
+
onUpdate: (forms: string[]) => {
|
|
915
|
+
display.updateFilteredTranscripts(forms)
|
|
916
|
+
},
|
|
917
|
+
},
|
|
918
|
+
],
|
|
799
919
|
)
|
|
800
|
-
session.showWidget(apolloTranscriptWidget)
|
|
801
920
|
},
|
|
802
921
|
})
|
|
803
922
|
}
|
|
@@ -10,6 +10,7 @@ import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/con
|
|
|
10
10
|
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes'
|
|
11
11
|
import {
|
|
12
12
|
type AbstractSessionModel,
|
|
13
|
+
type SessionWithWidgets,
|
|
13
14
|
getContainingView,
|
|
14
15
|
getSession,
|
|
15
16
|
} from '@jbrowse/core/util'
|
|
@@ -36,6 +37,7 @@ export function baseModelFactory(
|
|
|
36
37
|
configuration: ConfigurationReference(configSchema),
|
|
37
38
|
graphical: true,
|
|
38
39
|
table: false,
|
|
40
|
+
showFeatureLabels: true,
|
|
39
41
|
heightPreConfig: types.maybe(
|
|
40
42
|
types.refinement(
|
|
41
43
|
'displayHeight',
|
|
@@ -164,6 +166,9 @@ export function baseModelFactory(
|
|
|
164
166
|
self.graphical = true
|
|
165
167
|
self.table = true
|
|
166
168
|
},
|
|
169
|
+
toggleShowFeatureLabels() {
|
|
170
|
+
self.showFeatureLabels = !self.showFeatureLabels
|
|
171
|
+
},
|
|
167
172
|
updateFilteredFeatureTypes(types: string[]) {
|
|
168
173
|
self.filteredFeatureTypes = cast(types)
|
|
169
174
|
},
|
|
@@ -172,7 +177,7 @@ export function baseModelFactory(
|
|
|
172
177
|
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
|
|
173
178
|
return {
|
|
174
179
|
trackMenuItems() {
|
|
175
|
-
const { graphical, table } = self
|
|
180
|
+
const { graphical, table, showFeatureLabels } = self
|
|
176
181
|
return [
|
|
177
182
|
...superTrackMenuItems(),
|
|
178
183
|
{
|
|
@@ -203,6 +208,14 @@ export function baseModelFactory(
|
|
|
203
208
|
self.showGraphicalAndTable()
|
|
204
209
|
},
|
|
205
210
|
},
|
|
211
|
+
{
|
|
212
|
+
label: 'Feature Labels',
|
|
213
|
+
type: 'checkbox',
|
|
214
|
+
checked: showFeatureLabels,
|
|
215
|
+
onClick: () => {
|
|
216
|
+
self.toggleShowFeatureLabels()
|
|
217
|
+
},
|
|
218
|
+
},
|
|
206
219
|
],
|
|
207
220
|
},
|
|
208
221
|
{
|
|
@@ -236,6 +249,34 @@ export function baseModelFactory(
|
|
|
236
249
|
self.session as unknown as ApolloSessionModel
|
|
237
250
|
).apolloSetSelectedFeature(feature)
|
|
238
251
|
},
|
|
252
|
+
showFeatureDetailsWidget(
|
|
253
|
+
feature: AnnotationFeature,
|
|
254
|
+
customWidgetNameAndId?: [string, string],
|
|
255
|
+
) {
|
|
256
|
+
const [region] = self.regions
|
|
257
|
+
const { assemblyName, refName } = region
|
|
258
|
+
const assembly = self.getAssemblyId(assemblyName)
|
|
259
|
+
if (!assembly) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
const { session } = self
|
|
263
|
+
const { changeManager } = session.apolloDataStore
|
|
264
|
+
const [widgetName, widgetId] = customWidgetNameAndId ?? [
|
|
265
|
+
'ApolloFeatureDetailsWidget',
|
|
266
|
+
'apolloFeatureDetailsWidget',
|
|
267
|
+
]
|
|
268
|
+
const apolloFeatureWidget = (
|
|
269
|
+
session as unknown as SessionWithWidgets
|
|
270
|
+
).addWidget(widgetName, widgetId, {
|
|
271
|
+
feature,
|
|
272
|
+
assembly,
|
|
273
|
+
refName,
|
|
274
|
+
changeManager,
|
|
275
|
+
})
|
|
276
|
+
;(session as unknown as SessionWithWidgets).showWidget(
|
|
277
|
+
apolloFeatureWidget,
|
|
278
|
+
)
|
|
279
|
+
},
|
|
239
280
|
afterAttach() {
|
|
240
281
|
addDisposer(
|
|
241
282
|
self,
|