@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.
Files changed (192) hide show
  1. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts +1 -1
  2. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts.map +1 -1
  3. package/dist/BackendDrivers/BackendDriver.d.ts +29 -4
  4. package/dist/BackendDrivers/BackendDriver.d.ts.map +1 -1
  5. package/dist/BackendDrivers/CollaborationServerDriver.d.ts +3 -1
  6. package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
  7. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts +22 -0
  8. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -0
  9. package/dist/BackendDrivers/LocalDriver/db.d.ts +4 -0
  10. package/dist/BackendDrivers/LocalDriver/db.d.ts.map +1 -0
  11. package/dist/BackendDrivers/index.d.ts +1 -2
  12. package/dist/BackendDrivers/index.d.ts.map +1 -1
  13. package/dist/ChangeManager.d.ts +3 -3
  14. package/dist/ChangeManager.d.ts.map +1 -1
  15. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts +0 -6
  16. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
  17. package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
  18. package/dist/FeatureDetailsWidget/model.d.ts +0 -2
  19. package/dist/FeatureDetailsWidget/model.d.ts.map +1 -1
  20. package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
  21. package/dist/LinearApolloDisplay/components/LinearApolloDisplay.d.ts.map +1 -1
  22. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts +7 -0
  23. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts.map +1 -0
  24. package/dist/LinearApolloDisplay/components/Tooltip.d.ts +10 -0
  25. package/dist/LinearApolloDisplay/components/Tooltip.d.ts.map +1 -0
  26. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts +0 -1
  27. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts.map +1 -1
  28. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts +3 -0
  29. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts.map +1 -0
  30. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts +3 -0
  31. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -0
  32. package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  33. package/dist/LinearApolloDisplay/glyphs/GenericChildGlyph.d.ts.map +1 -1
  34. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts +26 -20
  35. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts.map +1 -1
  36. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts +3 -0
  37. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -0
  38. package/dist/LinearApolloDisplay/glyphs/util.d.ts +13 -0
  39. package/dist/LinearApolloDisplay/glyphs/util.d.ts.map +1 -1
  40. package/dist/LinearApolloDisplay/stateModel/base.d.ts +17 -0
  41. package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
  42. package/dist/LinearApolloDisplay/stateModel/index.d.ts +35 -17
  43. package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
  44. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +29 -7
  45. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
  46. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +69 -23
  47. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  48. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +26 -9
  49. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
  50. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +6 -0
  51. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
  52. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +6 -0
  53. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts.map +1 -1
  54. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +6 -0
  55. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts.map +1 -1
  56. package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
  57. package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  58. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts +1 -1
  59. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts.map +1 -1
  60. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
  61. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
  62. package/dist/OntologyManager/OntologyStore/fulltext.d.ts +1 -1
  63. package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
  64. package/dist/OntologyManager/OntologyStore/index.d.ts +2 -2
  65. package/dist/OntologyManager/OntologyStore/index.d.ts.map +1 -1
  66. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts +1 -1
  67. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts.map +1 -1
  68. package/dist/OntologyManager/OntologyStore/types.d.ts +18 -0
  69. package/dist/OntologyManager/OntologyStore/types.d.ts.map +1 -0
  70. package/dist/TabularEditor/HybridGrid/featureContextMenuItems.d.ts.map +1 -1
  71. package/dist/components/AddChildFeature.d.ts.map +1 -1
  72. package/dist/components/ColorFeature.d.ts +13 -0
  73. package/dist/components/ColorFeature.d.ts.map +1 -0
  74. package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
  75. package/dist/components/DownloadGFF3.d.ts +4 -1
  76. package/dist/components/DownloadGFF3.d.ts.map +1 -1
  77. package/dist/components/DuplicateTranscript.d.ts.map +1 -1
  78. package/dist/components/ViewChangeLog.d.ts +2 -1
  79. package/dist/components/ViewChangeLog.d.ts.map +1 -1
  80. package/dist/components/ViewCheckResults.d.ts +2 -1
  81. package/dist/components/ViewCheckResults.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -1
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/config.d.ts +4 -0
  85. package/dist/config.d.ts.map +1 -0
  86. package/dist/extensions/annotationFromJBrowseFeature.d.ts.map +1 -1
  87. package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
  88. package/dist/index.d.ts +11 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.esm.js +6325 -5997
  91. package/dist/index.esm.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.development.js +5869 -5541
  93. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  94. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  95. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  96. package/dist/jbrowse-plugin-apollo.umd.development.js +16782 -25897
  97. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  98. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  99. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  100. package/dist/makeDisplayComponent.d.ts.map +1 -1
  101. package/dist/menus/Icons.d.ts +3 -0
  102. package/dist/menus/Icons.d.ts.map +1 -0
  103. package/dist/menus/topLevelMenu.d.ts.map +1 -1
  104. package/dist/session/changeHandlers.d.ts +9 -0
  105. package/dist/session/changeHandlers.d.ts.map +1 -0
  106. package/dist/util/annotationFeatureUtils.d.ts +2 -1
  107. package/dist/util/annotationFeatureUtils.d.ts.map +1 -1
  108. package/dist/util/glyphUtils.d.ts +3 -3
  109. package/dist/util/glyphUtils.d.ts.map +1 -1
  110. package/dist/util/index.d.ts +0 -1
  111. package/dist/util/index.d.ts.map +1 -1
  112. package/package.json +4 -4
  113. package/src/ApolloInternetAccount/model.ts +68 -4
  114. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +6 -3
  115. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
  116. package/src/BackendDrivers/BackendDriver.ts +36 -3
  117. package/src/BackendDrivers/CollaborationServerDriver.ts +78 -23
  118. package/src/BackendDrivers/LocalDriver/LocalDriver.ts +367 -0
  119. package/src/BackendDrivers/LocalDriver/db.ts +37 -0
  120. package/src/BackendDrivers/index.ts +1 -2
  121. package/src/ChangeManager.ts +27 -25
  122. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +1 -1
  123. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +69 -53
  124. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +1 -5
  125. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +95 -115
  126. package/src/LinearApolloDisplay/components/OverlayCanvas.tsx +76 -0
  127. package/src/LinearApolloDisplay/components/Tooltip.tsx +42 -0
  128. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +60 -302
  129. package/src/LinearApolloDisplay/glyphs/CDSGlyph.ts +145 -0
  130. package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +212 -0
  131. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +65 -999
  132. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +71 -181
  133. package/src/LinearApolloDisplay/glyphs/Glyph.ts +42 -66
  134. package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +291 -0
  135. package/src/LinearApolloDisplay/glyphs/util.ts +87 -0
  136. package/src/LinearApolloDisplay/stateModel/base.ts +83 -0
  137. package/src/LinearApolloDisplay/stateModel/layouts.ts +198 -138
  138. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +252 -158
  139. package/src/LinearApolloDisplay/stateModel/rendering.ts +103 -21
  140. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +3 -3
  141. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +20 -2
  142. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
  143. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +8 -13
  144. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +1 -1
  145. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -3
  146. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +1 -1
  147. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +2 -1
  148. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18262 -8519
  149. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -2
  150. package/src/OntologyManager/OntologyStore/index.test.ts +5 -2
  151. package/src/OntologyManager/OntologyStore/index.ts +7 -8
  152. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +2 -2
  153. package/src/OntologyManager/OntologyStore/types.ts +27 -0
  154. package/src/OntologyManager/index.ts +15 -26
  155. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +4 -5
  156. package/src/components/AddChildFeature.tsx +15 -8
  157. package/src/components/ColorFeature.tsx +167 -0
  158. package/src/components/CreateApolloAnnotation.tsx +35 -9
  159. package/src/components/DownloadGFF3.tsx +92 -121
  160. package/src/components/DuplicateTranscript.tsx +10 -0
  161. package/src/components/ViewChangeLog.tsx +123 -83
  162. package/src/components/ViewCheckResults.tsx +15 -73
  163. package/src/components/index.ts +1 -1
  164. package/src/config.ts +37 -19
  165. package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -1
  166. package/src/extensions/annotationFromJBrowseFeature.ts +91 -63
  167. package/src/extensions/annotationFromPileup.ts +40 -40
  168. package/src/index.ts +45 -1
  169. package/src/makeDisplayComponent.tsx +10 -3
  170. package/src/menus/Icons.tsx +49 -0
  171. package/src/menus/topLevelMenu.ts +24 -96
  172. package/src/session/ClientDataStore.ts +16 -17
  173. package/src/session/changeHandlers.ts +261 -0
  174. package/src/session/session.ts +77 -46
  175. package/src/util/annotationFeatureUtils.ts +29 -1
  176. package/src/util/glyphUtils.ts +74 -31
  177. package/src/util/index.ts +0 -1
  178. package/dist/BackendDrivers/DesktopFileDriver.d.ts +0 -160
  179. package/dist/BackendDrivers/DesktopFileDriver.d.ts.map +0 -1
  180. package/dist/BackendDrivers/InMemoryFileDriver.d.ts +0 -162
  181. package/dist/BackendDrivers/InMemoryFileDriver.d.ts.map +0 -1
  182. package/dist/LinearApolloDisplay/glyphs/index.d.ts +0 -4
  183. package/dist/LinearApolloDisplay/glyphs/index.d.ts.map +0 -1
  184. package/dist/components/OpenLocalFile.d.ts +0 -15
  185. package/dist/components/OpenLocalFile.d.ts.map +0 -1
  186. package/dist/util/loadAssemblyIntoClient.d.ts +0 -5
  187. package/dist/util/loadAssemblyIntoClient.d.ts.map +0 -1
  188. package/src/BackendDrivers/DesktopFileDriver.ts +0 -184
  189. package/src/BackendDrivers/InMemoryFileDriver.ts +0 -107
  190. package/src/LinearApolloDisplay/glyphs/index.ts +0 -3
  191. package/src/components/OpenLocalFile.tsx +0 -189
  192. 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 type { OntologyRecord } from '../../OntologyManager'
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
- let forwardFillLight: CanvasPattern | null = null
43
- let backwardFillLight: CanvasPattern | null = null
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
- feature: AnnotationFeature,
21
+ gene: AnnotationFeature,
150
22
  row: number,
151
- stateModel: LinearApolloDisplayRendering,
152
- displayedRegionIndex: number,
23
+ rowInFeature: number,
24
+ block: ContentBlock,
153
25
  ): void {
154
- const { apolloRowHeight, lgv, selectedFeature, session, theme } = stateModel
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 { apolloDataStore } = session
165
- const { featureTypeOntology } = apolloDataStore.ontologyManager
166
- if (!featureTypeOntology) {
167
- throw new Error('featureTypeOntology is undefined')
168
- }
169
-
170
- // Draw background for gene
171
- drawBackground(ctx, feature, stateModel, displayedRegionIndex, row)
172
-
173
- // Draw lines on different rows for each transcript
174
- let currentRow = 0
175
- for (const [, transcript] of children) {
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
- function* range(start: number, stop: number, step = 1): Generator<number> {
399
- if (start === stop) {
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 drawLine(
49
+ function drawOverlay(
50
+ display: LinearApolloDisplay,
414
51
  ctx: CanvasRenderingContext2D,
415
- stateModel: LinearApolloDisplayRendering,
416
- displayedRegionIndex: number,
52
+ gene: AnnotationFeature,
417
53
  row: number,
418
- transcript: AnnotationFeature,
419
- currentRow: number,
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
- const { hoveredFeature } = stateModel
539
-
540
- if (!hoveredFeature) {
58
+ if (rowInFeature > 0) {
541
59
  return
542
60
  }
543
- drawHighlight(stateModel, ctx, hoveredFeature.feature)
544
- }
545
-
546
- function getFeatureFromLayout(
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 getRowCount(
610
- feature: AnnotationFeature,
611
- featureTypeOntology: OntologyRecord,
612
- _bpPerPx?: number,
613
- ): number {
614
- const { children, type } = feature
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 [[feature]]
76
+ return layout
657
77
  }
658
- const features: AnnotationFeature[][] = []
78
+ layout.byRow = []
79
+ // eslint-disable-next-line @typescript-eslint/unbound-method
80
+ const { getGlyph } = display
659
81
  for (const [, child] of children) {
660
- if (
661
- !(
662
- featureTypeOntology.isTypeOf(child.type, 'transcript') ||
663
- featureTypeOntology.isTypeOf(child.type, 'pseudogenic_transcript')
664
- )
665
- ) {
666
- features.push([child, feature])
667
- continue
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 overlappingExon = exonChildren.find((child) => {
804
- const [start, end] = intersection2(bp - 1, bp, child.min, child.max)
805
- return start !== undefined && end !== undefined
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
- // End of special cases, let's see if we're on the edge of this CDS or exon
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
- display: LinearApolloDisplayMouseEvents,
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 { drawTooltip, getContextMenuItemsForFeature, onMouseLeave } = boxGlyph
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
- drawHover,
1039
- drawTooltip,
111
+ drawOverlay,
1040
112
  getContextMenuItems,
1041
- getContextMenuItemsForFeature,
1042
- getFeatureFromLayout,
1043
- getRowCount,
1044
- getRowForFeature,
1045
- onMouseDown,
1046
- onMouseLeave,
1047
- onMouseMove,
1048
- onMouseUp,
113
+ getLayout,
114
+ isDraggable: false,
1049
115
  }