@apollo-annotation/jbrowse-plugin-apollo 0.3.12 → 1.0.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/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts +1 -1
- package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts.map +1 -1
- package/dist/BackendDrivers/BackendDriver.d.ts +29 -4
- package/dist/BackendDrivers/BackendDriver.d.ts.map +1 -1
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts +3 -1
- package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
- package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts +22 -0
- package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -0
- package/dist/BackendDrivers/LocalDriver/db.d.ts +4 -0
- package/dist/BackendDrivers/LocalDriver/db.d.ts.map +1 -0
- package/dist/BackendDrivers/index.d.ts +1 -2
- package/dist/BackendDrivers/index.d.ts.map +1 -1
- package/dist/ChangeManager.d.ts +3 -3
- package/dist/ChangeManager.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts +0 -6
- package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
- package/dist/FeatureDetailsWidget/model.d.ts +0 -2
- package/dist/FeatureDetailsWidget/model.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/components/LinearApolloDisplay.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts +7 -0
- package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/components/Tooltip.d.ts +10 -0
- package/dist/LinearApolloDisplay/components/Tooltip.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts +0 -1
- package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts +3 -0
- package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts +3 -0
- package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/GenericChildGlyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts +26 -20
- package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts +3 -0
- package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -0
- package/dist/LinearApolloDisplay/glyphs/util.d.ts +13 -0
- package/dist/LinearApolloDisplay/glyphs/util.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/base.d.ts +17 -0
- package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/index.d.ts +35 -17
- package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +29 -7
- package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +69 -23
- package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +26 -9
- package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +6 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +6 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts.map +1 -1
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +6 -0
- package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts +1 -1
- package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
- package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/fulltext.d.ts +1 -1
- package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/index.d.ts +2 -2
- package/dist/OntologyManager/OntologyStore/index.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts +1 -1
- package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts.map +1 -1
- package/dist/OntologyManager/OntologyStore/types.d.ts +18 -0
- package/dist/OntologyManager/OntologyStore/types.d.ts.map +1 -0
- package/dist/TabularEditor/HybridGrid/featureContextMenuItems.d.ts.map +1 -1
- package/dist/components/AddChildFeature.d.ts.map +1 -1
- package/dist/components/ColorFeature.d.ts +13 -0
- package/dist/components/ColorFeature.d.ts.map +1 -0
- package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
- package/dist/components/DownloadGFF3.d.ts +4 -1
- package/dist/components/DownloadGFF3.d.ts.map +1 -1
- package/dist/components/DuplicateTranscript.d.ts.map +1 -1
- package/dist/components/ViewChangeLog.d.ts +2 -1
- package/dist/components/ViewChangeLog.d.ts.map +1 -1
- package/dist/components/ViewCheckResults.d.ts +2 -1
- package/dist/components/ViewCheckResults.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/extensions/annotationFromJBrowseFeature.d.ts.map +1 -1
- package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +6325 -5997
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +5869 -5541
- 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 +16782 -25897
- 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/dist/makeDisplayComponent.d.ts.map +1 -1
- package/dist/menus/Icons.d.ts +3 -0
- package/dist/menus/Icons.d.ts.map +1 -0
- package/dist/menus/topLevelMenu.d.ts.map +1 -1
- package/dist/session/changeHandlers.d.ts +9 -0
- package/dist/session/changeHandlers.d.ts.map +1 -0
- package/dist/util/annotationFeatureUtils.d.ts +2 -1
- package/dist/util/annotationFeatureUtils.d.ts.map +1 -1
- package/dist/util/glyphUtils.d.ts +3 -3
- package/dist/util/glyphUtils.d.ts.map +1 -1
- package/dist/util/index.d.ts +0 -1
- package/dist/util/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ApolloInternetAccount/model.ts +68 -4
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +6 -3
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
- package/src/BackendDrivers/BackendDriver.ts +36 -3
- package/src/BackendDrivers/CollaborationServerDriver.ts +78 -23
- package/src/BackendDrivers/LocalDriver/LocalDriver.ts +367 -0
- package/src/BackendDrivers/LocalDriver/db.ts +37 -0
- package/src/BackendDrivers/index.ts +1 -2
- package/src/ChangeManager.ts +27 -25
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +1 -1
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +69 -53
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +1 -5
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +95 -115
- package/src/LinearApolloDisplay/components/OverlayCanvas.tsx +76 -0
- package/src/LinearApolloDisplay/components/Tooltip.tsx +42 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +60 -302
- package/src/LinearApolloDisplay/glyphs/CDSGlyph.ts +145 -0
- package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +212 -0
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +65 -999
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +71 -181
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +42 -66
- package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +291 -0
- package/src/LinearApolloDisplay/glyphs/util.ts +87 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +83 -0
- package/src/LinearApolloDisplay/stateModel/layouts.ts +198 -138
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +252 -158
- package/src/LinearApolloDisplay/stateModel/rendering.ts +103 -21
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +3 -3
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +20 -2
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +8 -13
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +1 -1
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +1 -1
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +2 -1
- package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18262 -8519
- package/src/OntologyManager/OntologyStore/fulltext.ts +1 -2
- package/src/OntologyManager/OntologyStore/index.test.ts +5 -2
- package/src/OntologyManager/OntologyStore/index.ts +7 -8
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +2 -2
- package/src/OntologyManager/OntologyStore/types.ts +27 -0
- package/src/OntologyManager/index.ts +15 -26
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +4 -5
- package/src/components/AddChildFeature.tsx +15 -8
- package/src/components/ColorFeature.tsx +167 -0
- package/src/components/CreateApolloAnnotation.tsx +35 -9
- package/src/components/DownloadGFF3.tsx +92 -121
- package/src/components/DuplicateTranscript.tsx +10 -0
- package/src/components/ViewChangeLog.tsx +123 -83
- package/src/components/ViewCheckResults.tsx +15 -73
- package/src/components/index.ts +1 -1
- package/src/config.ts +37 -19
- package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -1
- package/src/extensions/annotationFromJBrowseFeature.ts +91 -63
- package/src/extensions/annotationFromPileup.ts +40 -40
- package/src/index.ts +45 -1
- package/src/makeDisplayComponent.tsx +10 -3
- package/src/menus/Icons.tsx +49 -0
- package/src/menus/topLevelMenu.ts +24 -96
- package/src/session/ClientDataStore.ts +16 -17
- package/src/session/changeHandlers.ts +261 -0
- package/src/session/session.ts +77 -46
- package/src/util/annotationFeatureUtils.ts +29 -1
- package/src/util/glyphUtils.ts +74 -31
- package/src/util/index.ts +0 -1
- package/dist/BackendDrivers/DesktopFileDriver.d.ts +0 -160
- package/dist/BackendDrivers/DesktopFileDriver.d.ts.map +0 -1
- package/dist/BackendDrivers/InMemoryFileDriver.d.ts +0 -162
- package/dist/BackendDrivers/InMemoryFileDriver.d.ts.map +0 -1
- package/dist/LinearApolloDisplay/glyphs/index.d.ts +0 -4
- package/dist/LinearApolloDisplay/glyphs/index.d.ts.map +0 -1
- package/dist/components/OpenLocalFile.d.ts +0 -15
- package/dist/components/OpenLocalFile.d.ts.map +0 -1
- package/dist/util/loadAssemblyIntoClient.d.ts +0 -5
- package/dist/util/loadAssemblyIntoClient.d.ts.map +0 -1
- package/src/BackendDrivers/DesktopFileDriver.ts +0 -184
- package/src/BackendDrivers/InMemoryFileDriver.ts +0 -107
- package/src/LinearApolloDisplay/glyphs/index.ts +0 -3
- package/src/components/OpenLocalFile.tsx +0 -189
- package/src/util/loadAssemblyIntoClient.ts +0 -94
|
@@ -1,1049 +1,115 @@
|
|
|
1
1
|
import type { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
2
|
import { readConfObject } from '@jbrowse/core/configuration'
|
|
3
|
-
import type { BaseDisplayModel } from '@jbrowse/core/pluggableElementTypes'
|
|
4
3
|
import type { MenuItem } from '@jbrowse/core/ui'
|
|
5
|
-
import {
|
|
6
|
-
type AbstractSessionModel,
|
|
7
|
-
getContainingView,
|
|
8
|
-
getFrame,
|
|
9
|
-
intersection2,
|
|
10
|
-
isSessionModelWithWidgets,
|
|
11
|
-
} from '@jbrowse/core/util'
|
|
12
|
-
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
4
|
+
import type { ContentBlock } from '@jbrowse/core/util/blockTypes'
|
|
13
5
|
import { alpha } from '@mui/material'
|
|
14
6
|
|
|
15
|
-
import
|
|
16
|
-
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
|
|
17
|
-
import { DuplicateTranscript } from '../../components/DuplicateTranscript'
|
|
18
|
-
import {
|
|
19
|
-
type MousePosition,
|
|
20
|
-
type MousePositionWithFeature,
|
|
21
|
-
containsSelectedFeature,
|
|
22
|
-
getAdjacentExons,
|
|
23
|
-
getMinAndMaxPx,
|
|
24
|
-
getOverlappingEdge,
|
|
25
|
-
getStreamIcon,
|
|
26
|
-
isCDSFeature,
|
|
27
|
-
isExonFeature,
|
|
28
|
-
isMousePositionWithFeature,
|
|
29
|
-
isTranscriptFeature,
|
|
30
|
-
navToFeatureCenter,
|
|
31
|
-
selectFeatureAndOpenWidget,
|
|
32
|
-
} from '../../util'
|
|
33
|
-
import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
|
|
7
|
+
import { isSelectedFeature } from '../../util'
|
|
34
8
|
import type { LinearApolloDisplay } from '../stateModel'
|
|
35
|
-
import type { LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
|
|
36
|
-
import type { LinearApolloDisplayRendering } from '../stateModel/rendering'
|
|
37
|
-
import type { CanvasMouseEvent } from '../types'
|
|
38
9
|
|
|
39
10
|
import { boxGlyph } from './BoxGlyph'
|
|
40
|
-
import type { Glyph } from './Glyph'
|
|
11
|
+
import type { Glyph, OverlayType } from './Glyph'
|
|
12
|
+
import { drawOverlayBox, getFeatureBox, strokeRectInner } from './util'
|
|
41
13
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
let forwardFillDark: CanvasPattern | null = null
|
|
45
|
-
let backwardFillDark: CanvasPattern | null = null
|
|
46
|
-
const canvas = globalThis.document.createElement('canvas')
|
|
47
|
-
// @ts-expect-error getContext is undefined in the web worker
|
|
48
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
49
|
-
if (canvas?.getContext) {
|
|
50
|
-
for (const direction of ['forward', 'backward']) {
|
|
51
|
-
for (const themeMode of ['light', 'dark']) {
|
|
52
|
-
const canvas = document.createElement('canvas')
|
|
53
|
-
const canvasSize = 10
|
|
54
|
-
canvas.width = canvas.height = canvasSize
|
|
55
|
-
const ctx = canvas.getContext('2d')
|
|
56
|
-
if (ctx) {
|
|
57
|
-
const stripeColor1 =
|
|
58
|
-
themeMode === 'light' ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,0.75)'
|
|
59
|
-
const stripeColor2 =
|
|
60
|
-
themeMode === 'light' ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.50)'
|
|
61
|
-
const gradient =
|
|
62
|
-
direction === 'forward'
|
|
63
|
-
? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
|
|
64
|
-
: ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
|
|
65
|
-
gradient.addColorStop(0, stripeColor1)
|
|
66
|
-
gradient.addColorStop(0.25, stripeColor1)
|
|
67
|
-
gradient.addColorStop(0.25, stripeColor2)
|
|
68
|
-
gradient.addColorStop(0.5, stripeColor2)
|
|
69
|
-
gradient.addColorStop(0.5, stripeColor1)
|
|
70
|
-
gradient.addColorStop(0.75, stripeColor1)
|
|
71
|
-
gradient.addColorStop(0.75, stripeColor2)
|
|
72
|
-
gradient.addColorStop(1, stripeColor2)
|
|
73
|
-
ctx.fillStyle = gradient
|
|
74
|
-
ctx.fillRect(0, 0, 10, 10)
|
|
75
|
-
if (direction === 'forward') {
|
|
76
|
-
if (themeMode === 'light') {
|
|
77
|
-
forwardFillLight = ctx.createPattern(canvas, 'repeat')
|
|
78
|
-
} else {
|
|
79
|
-
forwardFillDark = ctx.createPattern(canvas, 'repeat')
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
if (themeMode === 'light') {
|
|
83
|
-
backwardFillLight = ctx.createPattern(canvas, 'repeat')
|
|
84
|
-
} else {
|
|
85
|
-
backwardFillDark = ctx.createPattern(canvas, 'repeat')
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function drawBackground(
|
|
94
|
-
ctx: CanvasRenderingContext2D,
|
|
95
|
-
feature: AnnotationFeature,
|
|
96
|
-
stateModel: LinearApolloDisplayRendering,
|
|
97
|
-
displayedRegionIndex: number,
|
|
98
|
-
row: number,
|
|
99
|
-
color?: string,
|
|
100
|
-
) {
|
|
101
|
-
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
102
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
103
|
-
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
104
|
-
const { refName, reversed } = displayedRegion
|
|
105
|
-
const { apolloDataStore } = session
|
|
106
|
-
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
107
|
-
if (!featureTypeOntology) {
|
|
108
|
-
throw new Error('featureTypeOntology is undefined')
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const topLevelFeatureMinX =
|
|
112
|
-
(lgv.bpToPx({
|
|
113
|
-
refName,
|
|
114
|
-
coord: feature.min,
|
|
115
|
-
regionNumber: displayedRegionIndex,
|
|
116
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
117
|
-
const topLevelFeatureWidthPx = feature.length / bpPerPx
|
|
118
|
-
const topLevelFeatureStartPx = reversed
|
|
119
|
-
? topLevelFeatureMinX - topLevelFeatureWidthPx
|
|
120
|
-
: topLevelFeatureMinX
|
|
121
|
-
const topLevelFeatureTop = row * apolloRowHeight
|
|
122
|
-
const topLevelFeatureHeight =
|
|
123
|
-
getRowCount(feature, featureTypeOntology) * apolloRowHeight
|
|
124
|
-
|
|
125
|
-
let selectedColor
|
|
126
|
-
if (color) {
|
|
127
|
-
selectedColor = color
|
|
128
|
-
} else {
|
|
129
|
-
selectedColor = readConfObject(
|
|
130
|
-
session.getPluginConfiguration(),
|
|
131
|
-
'geneBackgroundColor',
|
|
132
|
-
{ featureType: feature.type },
|
|
133
|
-
) as string
|
|
134
|
-
if (!selectedColor) {
|
|
135
|
-
selectedColor = alpha(theme.palette.background.paper, 0.6)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
ctx.fillStyle = selectedColor
|
|
139
|
-
ctx.fillRect(
|
|
140
|
-
topLevelFeatureStartPx,
|
|
141
|
-
topLevelFeatureTop,
|
|
142
|
-
topLevelFeatureWidthPx,
|
|
143
|
-
topLevelFeatureHeight,
|
|
144
|
-
)
|
|
14
|
+
function getRowCount(display: LinearApolloDisplay, feature: AnnotationFeature) {
|
|
15
|
+
return getLayout(display, feature).byRow.length
|
|
145
16
|
}
|
|
146
17
|
|
|
147
18
|
function draw(
|
|
19
|
+
display: LinearApolloDisplay,
|
|
148
20
|
ctx: CanvasRenderingContext2D,
|
|
149
|
-
|
|
21
|
+
gene: AnnotationFeature,
|
|
150
22
|
row: number,
|
|
151
|
-
|
|
152
|
-
|
|
23
|
+
rowInFeature: number,
|
|
24
|
+
block: ContentBlock,
|
|
153
25
|
): void {
|
|
154
|
-
|
|
155
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
156
|
-
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
157
|
-
const { refName, reversed } = displayedRegion
|
|
158
|
-
const rowHeight = apolloRowHeight
|
|
159
|
-
const cdsHeight = Math.round(0.9 * rowHeight)
|
|
160
|
-
const { children, strand } = feature
|
|
161
|
-
if (!children) {
|
|
26
|
+
if (rowInFeature > 0) {
|
|
162
27
|
return
|
|
163
28
|
}
|
|
164
|
-
const {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const isTranscript =
|
|
177
|
-
featureTypeOntology.isTypeOf(transcript.type, 'transcript') ||
|
|
178
|
-
featureTypeOntology.isTypeOf(transcript.type, 'pseudogenic_transcript')
|
|
179
|
-
if (!isTranscript) {
|
|
180
|
-
currentRow += 1
|
|
181
|
-
continue
|
|
182
|
-
}
|
|
183
|
-
const { children: transcriptChildren } = transcript
|
|
184
|
-
if (!transcriptChildren) {
|
|
185
|
-
continue
|
|
186
|
-
}
|
|
187
|
-
const cdsCount = getCDSCount(transcript, featureTypeOntology)
|
|
188
|
-
|
|
189
|
-
for (const [, childFeature] of transcriptChildren) {
|
|
190
|
-
if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
drawLine(
|
|
194
|
-
ctx,
|
|
195
|
-
stateModel,
|
|
196
|
-
displayedRegionIndex,
|
|
197
|
-
row,
|
|
198
|
-
transcript,
|
|
199
|
-
currentRow,
|
|
200
|
-
)
|
|
201
|
-
currentRow += 1
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (cdsCount === 0) {
|
|
205
|
-
drawLine(
|
|
206
|
-
ctx,
|
|
207
|
-
stateModel,
|
|
208
|
-
displayedRegionIndex,
|
|
209
|
-
row,
|
|
210
|
-
transcript,
|
|
211
|
-
currentRow,
|
|
212
|
-
)
|
|
213
|
-
currentRow += 1
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const forwardFill =
|
|
218
|
-
theme.palette.mode === 'dark' ? forwardFillDark : forwardFillLight
|
|
219
|
-
const backwardFill =
|
|
220
|
-
theme.palette.mode === 'dark' ? backwardFillDark : backwardFillLight
|
|
221
|
-
// Draw exon and CDS for each transcript
|
|
222
|
-
currentRow = 0
|
|
223
|
-
for (const [, child] of children) {
|
|
224
|
-
if (
|
|
225
|
-
!(
|
|
226
|
-
featureTypeOntology.isTypeOf(child.type, 'transcript') ||
|
|
227
|
-
featureTypeOntology.isTypeOf(child.type, 'pseudogenic_transcript')
|
|
228
|
-
)
|
|
229
|
-
) {
|
|
230
|
-
boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
|
|
231
|
-
currentRow += 1
|
|
232
|
-
continue
|
|
233
|
-
}
|
|
234
|
-
const cdsCount = getCDSCount(child, featureTypeOntology)
|
|
235
|
-
if (cdsCount != 0) {
|
|
236
|
-
for (const cdsRow of child.cdsLocations) {
|
|
237
|
-
const { children: transcriptChildren } = child
|
|
238
|
-
if (!transcriptChildren) {
|
|
239
|
-
continue
|
|
240
|
-
}
|
|
241
|
-
for (const [, exon] of transcriptChildren) {
|
|
242
|
-
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
243
|
-
continue
|
|
244
|
-
}
|
|
245
|
-
drawExon(
|
|
246
|
-
ctx,
|
|
247
|
-
stateModel,
|
|
248
|
-
displayedRegionIndex,
|
|
249
|
-
row,
|
|
250
|
-
exon,
|
|
251
|
-
currentRow,
|
|
252
|
-
strand,
|
|
253
|
-
forwardFill,
|
|
254
|
-
backwardFill,
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
for (const cds of cdsRow) {
|
|
258
|
-
const cdsWidthPx = (cds.max - cds.min) / bpPerPx
|
|
259
|
-
const minX =
|
|
260
|
-
(lgv.bpToPx({
|
|
261
|
-
refName,
|
|
262
|
-
coord: cds.min,
|
|
263
|
-
regionNumber: displayedRegionIndex,
|
|
264
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
265
|
-
const cdsStartPx = reversed ? minX - cdsWidthPx : minX
|
|
266
|
-
ctx.fillStyle = theme.palette.text.primary
|
|
267
|
-
const cdsTop =
|
|
268
|
-
(row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
|
|
269
|
-
ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
|
|
270
|
-
if (cdsWidthPx > 2) {
|
|
271
|
-
ctx.clearRect(
|
|
272
|
-
cdsStartPx + 1,
|
|
273
|
-
cdsTop + 1,
|
|
274
|
-
cdsWidthPx - 2,
|
|
275
|
-
cdsHeight - 2,
|
|
276
|
-
)
|
|
277
|
-
const frame = getFrame(
|
|
278
|
-
cds.min,
|
|
279
|
-
cds.max,
|
|
280
|
-
child.strand ?? 1,
|
|
281
|
-
cds.phase,
|
|
282
|
-
)
|
|
283
|
-
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
284
|
-
ctx.fillStyle = frameColor ?? 'black'
|
|
285
|
-
ctx.fillRect(
|
|
286
|
-
cdsStartPx + 1,
|
|
287
|
-
cdsTop + 1,
|
|
288
|
-
cdsWidthPx - 2,
|
|
289
|
-
cdsHeight - 2,
|
|
290
|
-
)
|
|
291
|
-
if (forwardFill && backwardFill && strand) {
|
|
292
|
-
const reversal = reversed ? -1 : 1
|
|
293
|
-
const [topFill, bottomFill] =
|
|
294
|
-
strand * reversal === 1
|
|
295
|
-
? [forwardFill, backwardFill]
|
|
296
|
-
: [backwardFill, forwardFill]
|
|
297
|
-
ctx.fillStyle = topFill
|
|
298
|
-
ctx.fillRect(
|
|
299
|
-
cdsStartPx + 1,
|
|
300
|
-
cdsTop + 1,
|
|
301
|
-
cdsWidthPx - 2,
|
|
302
|
-
(cdsHeight - 2) / 2,
|
|
303
|
-
)
|
|
304
|
-
ctx.fillStyle = bottomFill
|
|
305
|
-
ctx.fillRect(
|
|
306
|
-
cdsStartPx + 1,
|
|
307
|
-
cdsTop + (cdsHeight - 2) / 2,
|
|
308
|
-
cdsWidthPx - 2,
|
|
309
|
-
(cdsHeight - 2) / 2,
|
|
310
|
-
)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
currentRow += 1
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const { children: transcriptChildren } = child
|
|
319
|
-
// Draw exons for non-coding genes
|
|
320
|
-
if (cdsCount === 0 && transcriptChildren) {
|
|
321
|
-
for (const [, exon] of transcriptChildren) {
|
|
322
|
-
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
|
|
323
|
-
continue
|
|
324
|
-
}
|
|
325
|
-
drawExon(
|
|
326
|
-
ctx,
|
|
327
|
-
stateModel,
|
|
328
|
-
displayedRegionIndex,
|
|
329
|
-
row,
|
|
330
|
-
exon,
|
|
331
|
-
currentRow,
|
|
332
|
-
strand,
|
|
333
|
-
forwardFill,
|
|
334
|
-
backwardFill,
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
currentRow += 1
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (selectedFeature && containsSelectedFeature(feature, selectedFeature)) {
|
|
341
|
-
drawHighlight(stateModel, ctx, selectedFeature, true)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function drawExon(
|
|
346
|
-
ctx: CanvasRenderingContext2D,
|
|
347
|
-
stateModel: LinearApolloDisplayRendering,
|
|
348
|
-
displayedRegionIndex: number,
|
|
349
|
-
row: number,
|
|
350
|
-
exon: AnnotationFeature,
|
|
351
|
-
currentRow: number,
|
|
352
|
-
strand: number | undefined,
|
|
353
|
-
forwardFill: CanvasPattern | null,
|
|
354
|
-
backwardFill: CanvasPattern | null,
|
|
355
|
-
) {
|
|
356
|
-
const { apolloRowHeight, lgv, theme } = stateModel
|
|
357
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
358
|
-
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
359
|
-
const { refName, reversed } = displayedRegion
|
|
360
|
-
|
|
361
|
-
const minX =
|
|
362
|
-
(lgv.bpToPx({
|
|
363
|
-
refName,
|
|
364
|
-
coord: exon.min,
|
|
365
|
-
regionNumber: displayedRegionIndex,
|
|
366
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
367
|
-
const widthPx = exon.length / bpPerPx
|
|
368
|
-
const startPx = reversed ? minX - widthPx : minX
|
|
369
|
-
|
|
370
|
-
const top = (row + currentRow) * apolloRowHeight
|
|
371
|
-
const exonHeight = Math.round(0.6 * apolloRowHeight)
|
|
372
|
-
const exonTop = top + (apolloRowHeight - exonHeight) / 2
|
|
373
|
-
ctx.fillStyle = theme.palette.text.primary
|
|
374
|
-
ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
|
|
375
|
-
if (widthPx > 2) {
|
|
376
|
-
ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
377
|
-
ctx.fillStyle = 'rgb(211,211,211)'
|
|
378
|
-
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
|
|
379
|
-
if (forwardFill && backwardFill && strand) {
|
|
380
|
-
const reversal = reversed ? -1 : 1
|
|
381
|
-
const [topFill, bottomFill] =
|
|
382
|
-
strand * reversal === 1
|
|
383
|
-
? [forwardFill, backwardFill]
|
|
384
|
-
: [backwardFill, forwardFill]
|
|
385
|
-
ctx.fillStyle = topFill
|
|
386
|
-
ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, (exonHeight - 2) / 2)
|
|
387
|
-
ctx.fillStyle = bottomFill
|
|
388
|
-
ctx.fillRect(
|
|
389
|
-
startPx + 1,
|
|
390
|
-
exonTop + 1 + (exonHeight - 2) / 2,
|
|
391
|
-
widthPx - 2,
|
|
392
|
-
(exonHeight - 2) / 2,
|
|
393
|
-
)
|
|
394
|
-
}
|
|
29
|
+
const { apolloRowHeight, theme, selectedFeature, session } = display
|
|
30
|
+
const [top, left, width] = getFeatureBox(display, gene, row, block)
|
|
31
|
+
const height = getRowCount(display, gene) * apolloRowHeight
|
|
32
|
+
if (width > 2) {
|
|
33
|
+
let selectedColor = readConfObject(
|
|
34
|
+
session.getPluginConfiguration(),
|
|
35
|
+
'geneBackgroundColor',
|
|
36
|
+
{ featureType: gene.type },
|
|
37
|
+
) as string
|
|
38
|
+
selectedColor = alpha(theme.palette.background.paper, 0.6)
|
|
39
|
+
ctx.fillStyle = selectedColor
|
|
40
|
+
ctx.fillRect(left, top, width, height)
|
|
395
41
|
}
|
|
396
|
-
|
|
42
|
+
strokeRectInner(ctx, left, top, width, height, theme.palette.text.primary)
|
|
397
43
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return
|
|
401
|
-
}
|
|
402
|
-
if (start < stop) {
|
|
403
|
-
for (let i = start; i < stop; i += step) {
|
|
404
|
-
yield i
|
|
405
|
-
}
|
|
406
|
-
return
|
|
407
|
-
}
|
|
408
|
-
for (let i = start; i > stop; i -= step) {
|
|
409
|
-
yield i
|
|
44
|
+
if (isSelectedFeature(gene, selectedFeature)) {
|
|
45
|
+
drawOverlay(display, ctx, gene, row, block, 'select', rowInFeature)
|
|
410
46
|
}
|
|
411
47
|
}
|
|
412
48
|
|
|
413
|
-
function
|
|
49
|
+
function drawOverlay(
|
|
50
|
+
display: LinearApolloDisplay,
|
|
414
51
|
ctx: CanvasRenderingContext2D,
|
|
415
|
-
|
|
416
|
-
displayedRegionIndex: number,
|
|
52
|
+
gene: AnnotationFeature,
|
|
417
53
|
row: number,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const { apolloRowHeight, lgv, theme } = stateModel
|
|
422
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
423
|
-
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
424
|
-
const { refName, reversed } = displayedRegion
|
|
425
|
-
const minX =
|
|
426
|
-
(lgv.bpToPx({
|
|
427
|
-
refName,
|
|
428
|
-
coord: transcript.min,
|
|
429
|
-
regionNumber: displayedRegionIndex,
|
|
430
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
431
|
-
const widthPx = Math.round(transcript.length / bpPerPx)
|
|
432
|
-
const startPx = reversed ? minX - widthPx : minX
|
|
433
|
-
const height =
|
|
434
|
-
Math.round((currentRow + 1 / 2) * apolloRowHeight) + row * apolloRowHeight
|
|
435
|
-
ctx.strokeStyle = theme.palette.text.primary
|
|
436
|
-
const { strand = 1 } = transcript
|
|
437
|
-
ctx.beginPath()
|
|
438
|
-
// If view is reversed, draw forward as reverse and vice versa
|
|
439
|
-
const effectiveStrand = strand * (reversed ? -1 : 1)
|
|
440
|
-
// Draw the transcript line, and extend it out a bit on the 3` end
|
|
441
|
-
const lineStart = startPx - (effectiveStrand === -1 ? 5 : 0)
|
|
442
|
-
const lineEnd = startPx + widthPx + (effectiveStrand === -1 ? 0 : 5)
|
|
443
|
-
ctx.moveTo(lineStart, height)
|
|
444
|
-
ctx.lineTo(lineEnd, height)
|
|
445
|
-
// Now to draw arrows every 20 pixels along the line
|
|
446
|
-
// Make the arrow range a bit shorter to avoid an arrow hanging off the 5` end
|
|
447
|
-
const arrowsStart = lineStart + (effectiveStrand === -1 ? 0 : 3)
|
|
448
|
-
const arrowsEnd = lineEnd - (effectiveStrand === -1 ? 3 : 0)
|
|
449
|
-
// Offset determines if the arrows face left or right
|
|
450
|
-
const offset = effectiveStrand === -1 ? 3 : -3
|
|
451
|
-
const arrowRange =
|
|
452
|
-
effectiveStrand === -1
|
|
453
|
-
? range(arrowsStart, arrowsEnd, 20)
|
|
454
|
-
: range(arrowsEnd, arrowsStart, 20)
|
|
455
|
-
for (const arrowLocation of arrowRange) {
|
|
456
|
-
ctx.moveTo(arrowLocation + offset, height + offset)
|
|
457
|
-
ctx.lineTo(arrowLocation, height)
|
|
458
|
-
ctx.lineTo(arrowLocation + offset, height - offset)
|
|
459
|
-
}
|
|
460
|
-
ctx.stroke()
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function drawDragPreview(
|
|
464
|
-
stateModel: LinearApolloDisplay,
|
|
465
|
-
overlayCtx: CanvasRenderingContext2D,
|
|
466
|
-
) {
|
|
467
|
-
const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel
|
|
468
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
469
|
-
if (!apolloDragging) {
|
|
470
|
-
return
|
|
471
|
-
}
|
|
472
|
-
const { current, edge, feature, start } = apolloDragging
|
|
473
|
-
|
|
474
|
-
const row = Math.floor(start.y / apolloRowHeight)
|
|
475
|
-
const region = displayedRegions[start.regionNumber]
|
|
476
|
-
const rowCount = 1
|
|
477
|
-
const featureEdgeBp = region.reversed
|
|
478
|
-
? region.end - feature[edge]
|
|
479
|
-
: feature[edge] - region.start
|
|
480
|
-
const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
|
|
481
|
-
const rectX = Math.min(current.x, featureEdgePx)
|
|
482
|
-
const rectY = row * apolloRowHeight
|
|
483
|
-
const rectWidth = Math.abs(current.x - featureEdgePx)
|
|
484
|
-
const rectHeight = apolloRowHeight * rowCount
|
|
485
|
-
overlayCtx.strokeStyle = theme.palette.info.main
|
|
486
|
-
overlayCtx.setLineDash([6])
|
|
487
|
-
overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
|
|
488
|
-
overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
|
|
489
|
-
overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function drawHighlight(
|
|
493
|
-
stateModel: LinearApolloDisplayRendering,
|
|
494
|
-
ctx: CanvasRenderingContext2D,
|
|
495
|
-
feature: AnnotationFeature,
|
|
496
|
-
selected = false,
|
|
497
|
-
) {
|
|
498
|
-
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
499
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
500
|
-
|
|
501
|
-
const position = stateModel.getFeatureLayoutPosition(feature)
|
|
502
|
-
if (!position) {
|
|
503
|
-
return
|
|
504
|
-
}
|
|
505
|
-
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
506
|
-
const { featureRow, layoutIndex, layoutRow } = position
|
|
507
|
-
const displayedRegion = displayedRegions[layoutIndex]
|
|
508
|
-
const { refName, reversed } = displayedRegion
|
|
509
|
-
const { length, max, min } = feature
|
|
510
|
-
const startPx =
|
|
511
|
-
(lgv.bpToPx({
|
|
512
|
-
refName,
|
|
513
|
-
coord: reversed ? max : min,
|
|
514
|
-
regionNumber: layoutIndex,
|
|
515
|
-
})?.offsetPx ?? 0) - offsetPx
|
|
516
|
-
const row = layoutRow + featureRow
|
|
517
|
-
const top = row * apolloRowHeight
|
|
518
|
-
const widthPx = length / bpPerPx
|
|
519
|
-
ctx.fillStyle = selected
|
|
520
|
-
? theme.palette.action.disabled
|
|
521
|
-
: theme.palette.action.focus
|
|
522
|
-
|
|
523
|
-
if (!featureTypeOntology) {
|
|
524
|
-
throw new Error('featureTypeOntology is undefined')
|
|
525
|
-
}
|
|
526
|
-
ctx.fillRect(
|
|
527
|
-
startPx,
|
|
528
|
-
top,
|
|
529
|
-
widthPx,
|
|
530
|
-
apolloRowHeight * getRowCount(feature, featureTypeOntology),
|
|
531
|
-
)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function drawHover(
|
|
535
|
-
stateModel: LinearApolloDisplay,
|
|
536
|
-
ctx: CanvasRenderingContext2D,
|
|
54
|
+
block: ContentBlock,
|
|
55
|
+
overlayType: OverlayType,
|
|
56
|
+
rowInFeature: number,
|
|
537
57
|
) {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (!hoveredFeature) {
|
|
58
|
+
if (rowInFeature > 0) {
|
|
541
59
|
return
|
|
542
60
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
feature: AnnotationFeature,
|
|
548
|
-
bp: number,
|
|
549
|
-
row: number,
|
|
550
|
-
featureTypeOntology: OntologyRecord,
|
|
551
|
-
): AnnotationFeature | undefined {
|
|
552
|
-
const featureInThisRow: AnnotationFeature[] =
|
|
553
|
-
featuresForRow(feature, featureTypeOntology)[row] || []
|
|
554
|
-
for (const f of featureInThisRow) {
|
|
555
|
-
let featureObj
|
|
556
|
-
if (bp >= f.min && bp <= f.max && f.parent) {
|
|
557
|
-
featureObj = f
|
|
558
|
-
}
|
|
559
|
-
if (!featureObj) {
|
|
560
|
-
continue
|
|
561
|
-
}
|
|
562
|
-
if (
|
|
563
|
-
featureTypeOntology.isTypeOf(featureObj.type, 'CDS') &&
|
|
564
|
-
featureObj.parent &&
|
|
565
|
-
(featureTypeOntology.isTypeOf(featureObj.parent.type, 'transcript') ||
|
|
566
|
-
featureTypeOntology.isTypeOf(
|
|
567
|
-
featureObj.parent.type,
|
|
568
|
-
'pseudogenic_transcript',
|
|
569
|
-
))
|
|
570
|
-
) {
|
|
571
|
-
const { cdsLocations } = featureObj.parent
|
|
572
|
-
for (const cdsLoc of cdsLocations) {
|
|
573
|
-
for (const loc of cdsLoc) {
|
|
574
|
-
if (bp >= loc.min && bp <= loc.max) {
|
|
575
|
-
return featureObj
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// If mouse position is in the intron region, return the transcript
|
|
581
|
-
return featureObj.parent
|
|
582
|
-
}
|
|
583
|
-
// If mouse position is in a feature that is not a CDS, return the feature
|
|
584
|
-
return featureObj
|
|
585
|
-
}
|
|
586
|
-
return feature
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function getCDSCount(
|
|
590
|
-
feature: AnnotationFeature,
|
|
591
|
-
featureTypeOntology: OntologyRecord,
|
|
592
|
-
): number {
|
|
593
|
-
const { children, type } = feature
|
|
594
|
-
if (!children) {
|
|
595
|
-
return 0
|
|
596
|
-
}
|
|
597
|
-
const isMrna = featureTypeOntology.isTypeOf(type, 'transcript')
|
|
598
|
-
let cdsCount = 0
|
|
599
|
-
if (isMrna) {
|
|
600
|
-
for (const [, child] of children) {
|
|
601
|
-
if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
|
|
602
|
-
cdsCount += 1
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
return cdsCount
|
|
61
|
+
const { apolloRowHeight } = display
|
|
62
|
+
const [top, left, width] = getFeatureBox(display, gene, row, block)
|
|
63
|
+
const height = getRowCount(display, gene) * apolloRowHeight
|
|
64
|
+
drawOverlayBox(display, ctx, left, top, width, height, gene, overlayType)
|
|
607
65
|
}
|
|
608
66
|
|
|
609
|
-
function
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (!children) {
|
|
616
|
-
return 1
|
|
617
|
-
}
|
|
618
|
-
const isTranscript =
|
|
619
|
-
featureTypeOntology.isTypeOf(type, 'transcript') ||
|
|
620
|
-
featureTypeOntology.isTypeOf(type, 'pseudogenic_transcript')
|
|
621
|
-
let rowCount = 0
|
|
622
|
-
if (isTranscript) {
|
|
623
|
-
for (const [, child] of children) {
|
|
624
|
-
if (featureTypeOntology.isTypeOf(child.type, 'CDS')) {
|
|
625
|
-
rowCount += 1
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// return 1 if there are no CDSs for non coding genes
|
|
630
|
-
return rowCount === 0 ? 1 : rowCount
|
|
631
|
-
}
|
|
632
|
-
for (const [, child] of children) {
|
|
633
|
-
rowCount += getRowCount(child, featureTypeOntology)
|
|
634
|
-
}
|
|
635
|
-
return rowCount
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* A list of all the subfeatures for each row for a given feature, as well as
|
|
640
|
-
* the feature itself.
|
|
641
|
-
* If the row contains a transcript, the order is CDS -\> exon -\> transcript -\> gene
|
|
642
|
-
* If the row does not contain an transcript, the order is subfeature -\> gene
|
|
643
|
-
*/
|
|
644
|
-
function featuresForRow(
|
|
645
|
-
feature: AnnotationFeature,
|
|
646
|
-
featureTypeOntology: OntologyRecord,
|
|
647
|
-
): AnnotationFeature[][] {
|
|
648
|
-
const isGene =
|
|
649
|
-
featureTypeOntology.isTypeOf(feature.type, 'gene') ||
|
|
650
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogene')
|
|
651
|
-
if (!isGene) {
|
|
652
|
-
throw new Error('Top level feature for GeneGlyph must have type "gene"')
|
|
67
|
+
function getLayout(display: LinearApolloDisplay, feature: AnnotationFeature) {
|
|
68
|
+
const layout = {
|
|
69
|
+
byFeature: new Map([[feature._id, 0]]),
|
|
70
|
+
byRow: [[{ feature, rowInFeature: 0 }]],
|
|
71
|
+
min: feature.min,
|
|
72
|
+
max: feature.max,
|
|
653
73
|
}
|
|
654
74
|
const { children } = feature
|
|
655
75
|
if (!children) {
|
|
656
|
-
return
|
|
76
|
+
return layout
|
|
657
77
|
}
|
|
658
|
-
|
|
78
|
+
layout.byRow = []
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
80
|
+
const { getGlyph } = display
|
|
659
81
|
for (const [, child] of children) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
if (!child.children) {
|
|
670
|
-
continue
|
|
671
|
-
}
|
|
672
|
-
const cdss: AnnotationFeature[] = []
|
|
673
|
-
const exons: AnnotationFeature[] = []
|
|
674
|
-
for (const [, grandchild] of child.children) {
|
|
675
|
-
if (featureTypeOntology.isTypeOf(grandchild.type, 'CDS')) {
|
|
676
|
-
cdss.push(grandchild)
|
|
677
|
-
} else if (featureTypeOntology.isTypeOf(grandchild.type, 'exon')) {
|
|
678
|
-
exons.push(grandchild)
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
for (const cds of cdss) {
|
|
682
|
-
features.push([cds, ...exons, child, feature])
|
|
683
|
-
}
|
|
684
|
-
if (cdss.length === 0) {
|
|
685
|
-
features.push([...exons, child, feature])
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return features
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function getRowForFeature(
|
|
692
|
-
feature: AnnotationFeature,
|
|
693
|
-
childFeature: AnnotationFeature,
|
|
694
|
-
featureTypeOntology: OntologyRecord,
|
|
695
|
-
) {
|
|
696
|
-
const rows = featuresForRow(feature, featureTypeOntology)
|
|
697
|
-
for (const [idx, row] of rows.entries()) {
|
|
698
|
-
if (row.some((feature) => feature._id === childFeature._id)) {
|
|
699
|
-
return idx
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
return
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
function onMouseDown(
|
|
706
|
-
stateModel: LinearApolloDisplay,
|
|
707
|
-
currentMousePosition: MousePositionWithFeature,
|
|
708
|
-
event: CanvasMouseEvent,
|
|
709
|
-
) {
|
|
710
|
-
const { feature } = currentMousePosition
|
|
711
|
-
// swallow the mouseDown if we are on the edge of the feature so that we
|
|
712
|
-
// don't start dragging the view if we try to drag the feature edge
|
|
713
|
-
const draggableFeature = getDraggableFeatureInfo(
|
|
714
|
-
currentMousePosition,
|
|
715
|
-
feature,
|
|
716
|
-
stateModel,
|
|
717
|
-
)
|
|
718
|
-
if (draggableFeature) {
|
|
719
|
-
event.stopPropagation()
|
|
720
|
-
stateModel.startDrag(
|
|
721
|
-
currentMousePosition,
|
|
722
|
-
draggableFeature.feature,
|
|
723
|
-
draggableFeature.edge,
|
|
724
|
-
true,
|
|
725
|
-
)
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function onMouseMove(
|
|
730
|
-
stateModel: LinearApolloDisplay,
|
|
731
|
-
mousePosition: MousePosition,
|
|
732
|
-
) {
|
|
733
|
-
if (isMousePositionWithFeature(mousePosition)) {
|
|
734
|
-
const { feature, bp } = mousePosition
|
|
735
|
-
stateModel.setHoveredFeature({ feature, bp })
|
|
736
|
-
const draggableFeature = getDraggableFeatureInfo(
|
|
737
|
-
mousePosition,
|
|
738
|
-
feature,
|
|
739
|
-
stateModel,
|
|
740
|
-
)
|
|
741
|
-
if (draggableFeature) {
|
|
742
|
-
stateModel.setCursor('col-resize')
|
|
743
|
-
return
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
stateModel.setCursor()
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function onMouseUp(
|
|
750
|
-
stateModel: LinearApolloDisplay,
|
|
751
|
-
mousePosition: MousePosition,
|
|
752
|
-
) {
|
|
753
|
-
if (stateModel.apolloDragging) {
|
|
754
|
-
return
|
|
755
|
-
}
|
|
756
|
-
const { feature } = mousePosition
|
|
757
|
-
if (!feature) {
|
|
758
|
-
return
|
|
759
|
-
}
|
|
760
|
-
selectFeatureAndOpenWidget(stateModel, feature)
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function getDraggableFeatureInfo(
|
|
764
|
-
mousePosition: MousePosition,
|
|
765
|
-
feature: AnnotationFeature,
|
|
766
|
-
stateModel: LinearApolloDisplay,
|
|
767
|
-
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
|
|
768
|
-
const { session } = stateModel
|
|
769
|
-
const { apolloDataStore } = session
|
|
770
|
-
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
771
|
-
if (!featureTypeOntology) {
|
|
772
|
-
throw new Error('featureTypeOntology is undefined')
|
|
773
|
-
}
|
|
774
|
-
const isGene =
|
|
775
|
-
featureTypeOntology.isTypeOf(feature.type, 'gene') ||
|
|
776
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogene')
|
|
777
|
-
const isTranscript =
|
|
778
|
-
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
779
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
|
|
780
|
-
const isCDS = featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
781
|
-
if (isGene || isTranscript) {
|
|
782
|
-
// For gene glyphs, the sizes of genes and transcripts are determined by
|
|
783
|
-
// their child exons, so we don't make them draggable
|
|
784
|
-
return
|
|
785
|
-
}
|
|
786
|
-
// So now the type of feature is either CDS or exon. If an exon and CDS edge
|
|
787
|
-
// are in the same place, we want to prioritize dragging the exon. If the
|
|
788
|
-
// feature we're on is a CDS, let's find any exon it may overlap.
|
|
789
|
-
const { bp, refName, regionNumber, x } = mousePosition
|
|
790
|
-
const { lgv } = stateModel
|
|
791
|
-
if (isCDS) {
|
|
792
|
-
const transcript = feature.parent
|
|
793
|
-
if (!transcript?.children) {
|
|
794
|
-
return
|
|
795
|
-
}
|
|
796
|
-
const exonChildren: AnnotationFeature[] = []
|
|
797
|
-
for (const child of transcript.children.values()) {
|
|
798
|
-
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
799
|
-
if (childIsExon) {
|
|
800
|
-
exonChildren.push(child)
|
|
801
|
-
}
|
|
82
|
+
const glyph = getGlyph(child)
|
|
83
|
+
const childLayout = glyph.getLayout(display, child)
|
|
84
|
+
const startingRowIndex = layout.byRow.length
|
|
85
|
+
for (const [idx, row] of childLayout.byRow.entries()) {
|
|
86
|
+
layout.byRow.push([
|
|
87
|
+
{ feature, rowInFeature: startingRowIndex + idx },
|
|
88
|
+
...row,
|
|
89
|
+
])
|
|
802
90
|
}
|
|
803
|
-
const
|
|
804
|
-
const [
|
|
805
|
-
|
|
806
|
-
})
|
|
807
|
-
if (overlappingExon) {
|
|
808
|
-
// We are on an exon, are we on the edge of it?
|
|
809
|
-
const minMax = getMinAndMaxPx(overlappingExon, refName, regionNumber, lgv)
|
|
810
|
-
if (minMax) {
|
|
811
|
-
const overlappingEdge = getOverlappingEdge(overlappingExon, x, minMax)
|
|
812
|
-
if (overlappingEdge) {
|
|
813
|
-
return overlappingEdge
|
|
814
|
-
}
|
|
815
|
-
}
|
|
91
|
+
for (const entry of childLayout.byFeature.entries()) {
|
|
92
|
+
const [featureId, rowNumber] = entry
|
|
93
|
+
layout.byFeature.set(featureId, rowNumber + startingRowIndex)
|
|
816
94
|
}
|
|
817
95
|
}
|
|
818
|
-
|
|
819
|
-
const minMax = getMinAndMaxPx(feature, refName, regionNumber, lgv)
|
|
820
|
-
if (minMax) {
|
|
821
|
-
const overlappingEdge = getOverlappingEdge(feature, x, minMax)
|
|
822
|
-
if (overlappingEdge) {
|
|
823
|
-
return overlappingEdge
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
return
|
|
96
|
+
return layout
|
|
827
97
|
}
|
|
828
98
|
|
|
829
|
-
function getContextMenuItems(
|
|
830
|
-
|
|
831
|
-
mousePosition: MousePositionWithFeature,
|
|
832
|
-
): MenuItem[] {
|
|
833
|
-
const {
|
|
834
|
-
apolloInternetAccount: internetAccount,
|
|
835
|
-
hoveredFeature,
|
|
836
|
-
changeManager,
|
|
837
|
-
regions,
|
|
838
|
-
selectedFeature,
|
|
839
|
-
session,
|
|
840
|
-
} = display
|
|
841
|
-
const [region] = regions
|
|
842
|
-
const currentAssemblyId = display.getAssemblyId(region.assemblyName)
|
|
843
|
-
const menuItems: MenuItem[] = []
|
|
844
|
-
const role = internetAccount ? internetAccount.role : 'admin'
|
|
845
|
-
const admin = role === 'admin'
|
|
846
|
-
if (!hoveredFeature) {
|
|
847
|
-
return menuItems
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
if (isMousePositionWithFeature(mousePosition)) {
|
|
851
|
-
const { bp, feature } = mousePosition
|
|
852
|
-
let featuresUnderClick = getRelatedFeatures(feature, bp)
|
|
853
|
-
if (isCDSFeature(feature, session)) {
|
|
854
|
-
featuresUnderClick = getRelatedFeatures(feature, bp, true)
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
for (const feature of featuresUnderClick) {
|
|
858
|
-
const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
|
|
859
|
-
display,
|
|
860
|
-
feature,
|
|
861
|
-
)
|
|
862
|
-
if (isExonFeature(feature, session)) {
|
|
863
|
-
const adjacentExons = getAdjacentExons(
|
|
864
|
-
feature,
|
|
865
|
-
display,
|
|
866
|
-
mousePosition,
|
|
867
|
-
session,
|
|
868
|
-
)
|
|
869
|
-
const lgv = getContainingView(
|
|
870
|
-
display as BaseDisplayModel,
|
|
871
|
-
) as unknown as LinearGenomeViewModel
|
|
872
|
-
if (adjacentExons.upstream) {
|
|
873
|
-
const exon = adjacentExons.upstream
|
|
874
|
-
contextMenuItemsForFeature.push({
|
|
875
|
-
label: 'Go to upstream exon',
|
|
876
|
-
icon: getStreamIcon(
|
|
877
|
-
feature.strand,
|
|
878
|
-
true,
|
|
879
|
-
lgv.displayedRegions.at(0)?.reversed,
|
|
880
|
-
),
|
|
881
|
-
onClick: () => {
|
|
882
|
-
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
883
|
-
selectFeatureAndOpenWidget(display, exon)
|
|
884
|
-
},
|
|
885
|
-
})
|
|
886
|
-
}
|
|
887
|
-
if (adjacentExons.downstream) {
|
|
888
|
-
const exon = adjacentExons.downstream
|
|
889
|
-
contextMenuItemsForFeature.push({
|
|
890
|
-
label: 'Go to downstream exon',
|
|
891
|
-
icon: getStreamIcon(
|
|
892
|
-
feature.strand,
|
|
893
|
-
false,
|
|
894
|
-
lgv.displayedRegions.at(0)?.reversed,
|
|
895
|
-
),
|
|
896
|
-
onClick: () => {
|
|
897
|
-
lgv.navTo(navToFeatureCenter(exon, 0.1, lgv.totalBp))
|
|
898
|
-
selectFeatureAndOpenWidget(display, exon)
|
|
899
|
-
},
|
|
900
|
-
})
|
|
901
|
-
}
|
|
902
|
-
contextMenuItemsForFeature.push(
|
|
903
|
-
{
|
|
904
|
-
label: 'Merge exons',
|
|
905
|
-
disabled: !admin,
|
|
906
|
-
onClick: () => {
|
|
907
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
908
|
-
(doneCallback) => [
|
|
909
|
-
MergeExons,
|
|
910
|
-
{
|
|
911
|
-
session,
|
|
912
|
-
handleClose: () => {
|
|
913
|
-
doneCallback()
|
|
914
|
-
},
|
|
915
|
-
changeManager,
|
|
916
|
-
sourceFeature: feature,
|
|
917
|
-
sourceAssemblyId: currentAssemblyId,
|
|
918
|
-
selectedFeature,
|
|
919
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
920
|
-
display.setSelectedFeature(feature)
|
|
921
|
-
},
|
|
922
|
-
},
|
|
923
|
-
],
|
|
924
|
-
)
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
{
|
|
928
|
-
label: 'Split exon',
|
|
929
|
-
disabled: !admin,
|
|
930
|
-
onClick: () => {
|
|
931
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
932
|
-
(doneCallback) => [
|
|
933
|
-
SplitExon,
|
|
934
|
-
{
|
|
935
|
-
session,
|
|
936
|
-
handleClose: () => {
|
|
937
|
-
doneCallback()
|
|
938
|
-
},
|
|
939
|
-
changeManager,
|
|
940
|
-
sourceFeature: feature,
|
|
941
|
-
sourceAssemblyId: currentAssemblyId,
|
|
942
|
-
selectedFeature,
|
|
943
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
944
|
-
display.setSelectedFeature(feature)
|
|
945
|
-
},
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
)
|
|
949
|
-
},
|
|
950
|
-
},
|
|
951
|
-
)
|
|
952
|
-
}
|
|
953
|
-
if (isTranscriptFeature(feature, session)) {
|
|
954
|
-
contextMenuItemsForFeature.push(
|
|
955
|
-
{
|
|
956
|
-
label: 'Merge transcript',
|
|
957
|
-
onClick: () => {
|
|
958
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
959
|
-
(doneCallback) => [
|
|
960
|
-
MergeTranscripts,
|
|
961
|
-
{
|
|
962
|
-
session,
|
|
963
|
-
handleClose: () => {
|
|
964
|
-
doneCallback()
|
|
965
|
-
},
|
|
966
|
-
changeManager,
|
|
967
|
-
sourceFeature: feature,
|
|
968
|
-
sourceAssemblyId: currentAssemblyId,
|
|
969
|
-
selectedFeature,
|
|
970
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
971
|
-
display.setSelectedFeature(feature)
|
|
972
|
-
},
|
|
973
|
-
},
|
|
974
|
-
],
|
|
975
|
-
)
|
|
976
|
-
},
|
|
977
|
-
},
|
|
978
|
-
{
|
|
979
|
-
label: 'Duplicate feature',
|
|
980
|
-
onClick: () => {
|
|
981
|
-
;(session as unknown as AbstractSessionModel).queueDialog(
|
|
982
|
-
(doneCallback) => [
|
|
983
|
-
DuplicateTranscript,
|
|
984
|
-
{
|
|
985
|
-
session,
|
|
986
|
-
handleClose: () => {
|
|
987
|
-
doneCallback()
|
|
988
|
-
},
|
|
989
|
-
changeManager,
|
|
990
|
-
sourceFeature: feature,
|
|
991
|
-
sourceAssemblyId: currentAssemblyId,
|
|
992
|
-
selectedFeature,
|
|
993
|
-
setSelectedFeature: (feature?: AnnotationFeature) => {
|
|
994
|
-
display.setSelectedFeature(feature)
|
|
995
|
-
},
|
|
996
|
-
},
|
|
997
|
-
],
|
|
998
|
-
)
|
|
999
|
-
},
|
|
1000
|
-
},
|
|
1001
|
-
)
|
|
1002
|
-
if (isSessionModelWithWidgets(session)) {
|
|
1003
|
-
contextMenuItemsForFeature.splice(1, 0, {
|
|
1004
|
-
label: 'Open transcript editor',
|
|
1005
|
-
onClick: () => {
|
|
1006
|
-
const apolloTranscriptWidget = session.addWidget(
|
|
1007
|
-
'ApolloTranscriptDetails',
|
|
1008
|
-
'apolloTranscriptDetails',
|
|
1009
|
-
{
|
|
1010
|
-
feature,
|
|
1011
|
-
assembly: currentAssemblyId,
|
|
1012
|
-
changeManager,
|
|
1013
|
-
refName: region.refName,
|
|
1014
|
-
},
|
|
1015
|
-
)
|
|
1016
|
-
session.showWidget(apolloTranscriptWidget)
|
|
1017
|
-
},
|
|
1018
|
-
})
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
menuItems.push({
|
|
1022
|
-
label: feature.type,
|
|
1023
|
-
subMenu: contextMenuItemsForFeature,
|
|
1024
|
-
})
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
return menuItems
|
|
99
|
+
function getContextMenuItems(): MenuItem[] {
|
|
100
|
+
return []
|
|
1028
101
|
}
|
|
1029
102
|
|
|
1030
103
|
// False positive here, none of these functions use "this"
|
|
1031
104
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
1032
|
-
const {
|
|
105
|
+
const { drawDragPreview } = boxGlyph
|
|
1033
106
|
/* eslint-enable @typescript-eslint/unbound-method */
|
|
1034
107
|
|
|
1035
108
|
export const geneGlyph: Glyph = {
|
|
1036
109
|
draw,
|
|
1037
110
|
drawDragPreview,
|
|
1038
|
-
|
|
1039
|
-
drawTooltip,
|
|
111
|
+
drawOverlay,
|
|
1040
112
|
getContextMenuItems,
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
getRowCount,
|
|
1044
|
-
getRowForFeature,
|
|
1045
|
-
onMouseDown,
|
|
1046
|
-
onMouseLeave,
|
|
1047
|
-
onMouseMove,
|
|
1048
|
-
onMouseUp,
|
|
113
|
+
getLayout,
|
|
114
|
+
isDraggable: false,
|
|
1049
115
|
}
|