@apollo-annotation/jbrowse-plugin-apollo 0.1.18 → 0.1.20

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 (85) hide show
  1. package/dist/index.esm.js +3189 -3575
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +3185 -3570
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +14884 -15905
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +33 -33
  12. package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
  13. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
  14. package/src/ApolloInternetAccount/configSchema.ts +5 -2
  15. package/src/ApolloInternetAccount/model.ts +14 -5
  16. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +94 -0
  17. package/src/ApolloRefNameAliasAdapter/configSchema.ts +12 -0
  18. package/src/ApolloRefNameAliasAdapter/index.ts +21 -0
  19. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +1 -0
  20. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +10 -10
  21. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +35 -32
  22. package/src/BackendDrivers/BackendDriver.ts +8 -0
  23. package/src/BackendDrivers/CollaborationServerDriver.ts +49 -1
  24. package/src/BackendDrivers/DesktopFileDriver.ts +14 -1
  25. package/src/BackendDrivers/InMemoryFileDriver.ts +17 -1
  26. package/src/ChangeManager.ts +1 -1
  27. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +5 -25
  28. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +82 -0
  29. package/src/FeatureDetailsWidget/Attributes.tsx +11 -3
  30. package/src/FeatureDetailsWidget/BasicInformation.tsx +38 -30
  31. package/src/FeatureDetailsWidget/Sequence.tsx +7 -7
  32. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +446 -0
  33. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +365 -0
  34. package/src/FeatureDetailsWidget/index.ts +2 -0
  35. package/src/FeatureDetailsWidget/model.ts +77 -9
  36. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -2
  37. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +453 -380
  38. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +520 -0
  39. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +138 -134
  40. package/src/LinearApolloDisplay/glyphs/Glyph.ts +38 -370
  41. package/src/LinearApolloDisplay/glyphs/index.ts +1 -2
  42. package/src/LinearApolloDisplay/stateModel/base.ts +3 -6
  43. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +30 -30
  44. package/src/LinearApolloDisplay/stateModel/index.ts +5 -1
  45. package/src/LinearApolloDisplay/stateModel/layouts.ts +32 -24
  46. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +206 -217
  47. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -67
  48. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -1
  49. package/src/OntologyManager/OntologyStore/index.ts +2 -1
  50. package/src/OntologyManager/index.ts +6 -2
  51. package/src/OntologyManager/util.ts +2 -2
  52. package/src/SixFrameFeatureDisplay/stateModel.ts +15 -10
  53. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +21 -46
  54. package/src/TabularEditor/HybridGrid/Feature.tsx +31 -82
  55. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +3 -2
  56. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +2 -3
  57. package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
  58. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +46 -5
  59. package/src/TabularEditor/model.ts +5 -3
  60. package/src/components/AddAssembly.tsx +15 -9
  61. package/src/components/AddChildFeature.tsx +7 -73
  62. package/src/components/AddFeature.tsx +2 -57
  63. package/src/components/AddRefSeqAliases.tsx +285 -0
  64. package/src/components/CopyFeature.tsx +16 -33
  65. package/src/components/DeleteFeature.tsx +4 -6
  66. package/src/components/ImportFeatures.tsx +6 -3
  67. package/src/components/LogOut.tsx +105 -0
  68. package/src/components/ManageChecks.tsx +1 -0
  69. package/src/components/ManageUsers.tsx +21 -1
  70. package/src/components/ModifyFeatureAttribute.tsx +2 -2
  71. package/src/components/OntologyTermAutocomplete.tsx +0 -2
  72. package/src/components/OntologyTermMultiSelect.tsx +1 -0
  73. package/src/components/OpenLocalFile.tsx +6 -5
  74. package/src/components/ViewChangeLog.tsx +1 -0
  75. package/src/components/ViewCheckResults.tsx +1 -0
  76. package/src/components/index.ts +4 -0
  77. package/src/extensions/annotationFromPileup.ts +10 -16
  78. package/src/index.ts +57 -3
  79. package/src/session/ClientDataStore.ts +49 -46
  80. package/src/session/session.ts +186 -114
  81. package/src/util/loadAssemblyIntoClient.ts +4 -210
  82. package/src/FeatureDetailsWidget/RelatedFeature.tsx +0 -97
  83. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +0 -1204
  84. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +0 -716
  85. package/src/LinearApolloDisplay/stateModel/glyphs.ts +0 -47
@@ -0,0 +1,520 @@
1
+ import { AnnotationFeature } from '@apollo-annotation/mst'
2
+ import { getFrame, intersection2 } from '@jbrowse/core/util'
3
+ import { alpha } from '@mui/material'
4
+
5
+ import { LinearApolloDisplay } from '../stateModel'
6
+ import {
7
+ isMousePositionWithFeatureAndGlyph,
8
+ MousePosition,
9
+ MousePositionWithFeatureAndGlyph,
10
+ } from '../stateModel/mouseEvents'
11
+ import { CanvasMouseEvent } from '../types'
12
+ import { Glyph } from './Glyph'
13
+ import { boxGlyph } from './BoxGlyph'
14
+ import { LinearApolloDisplayRendering } from '../stateModel/rendering'
15
+
16
+ let forwardFill: CanvasPattern | null = null
17
+ let backwardFill: CanvasPattern | null = null
18
+ if ('document' in window) {
19
+ for (const direction of ['forward', 'backward']) {
20
+ const canvas = document.createElement('canvas')
21
+ const canvasSize = 10
22
+ canvas.width = canvas.height = canvasSize
23
+ const ctx = canvas.getContext('2d')
24
+ if (ctx) {
25
+ const stripeColor1 = 'rgba(0,0,0,0)'
26
+ const stripeColor2 = 'rgba(255,255,255,0.25)'
27
+ const gradient =
28
+ direction === 'forward'
29
+ ? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
30
+ : ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
31
+ gradient.addColorStop(0, stripeColor1)
32
+ gradient.addColorStop(0.25, stripeColor1)
33
+ gradient.addColorStop(0.25, stripeColor2)
34
+ gradient.addColorStop(0.5, stripeColor2)
35
+ gradient.addColorStop(0.5, stripeColor1)
36
+ gradient.addColorStop(0.75, stripeColor1)
37
+ gradient.addColorStop(0.75, stripeColor2)
38
+ gradient.addColorStop(1, stripeColor2)
39
+ ctx.fillStyle = gradient
40
+ ctx.fillRect(0, 0, 10, 10)
41
+ if (direction === 'forward') {
42
+ forwardFill = ctx.createPattern(canvas, 'repeat')
43
+ } else {
44
+ backwardFill = ctx.createPattern(canvas, 'repeat')
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ function draw(
51
+ ctx: CanvasRenderingContext2D,
52
+ feature: AnnotationFeature,
53
+ row: number,
54
+ stateModel: LinearApolloDisplayRendering,
55
+ displayedRegionIndex: number,
56
+ ): void {
57
+ const { apolloRowHeight, lgv, session, theme } = stateModel
58
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
59
+ const displayedRegion = displayedRegions[displayedRegionIndex]
60
+ const { refName, reversed } = displayedRegion
61
+ const rowHeight = apolloRowHeight
62
+ const exonHeight = Math.round(0.6 * rowHeight)
63
+ const cdsHeight = Math.round(0.9 * rowHeight)
64
+ const { strand } = feature
65
+ const { children } = feature
66
+ if (!children) {
67
+ return
68
+ }
69
+ const { apolloSelectedFeature } = session
70
+
71
+ // Draw lines on different rows for each mRNA
72
+ let currentRow = 0
73
+ for (const [, mrna] of children) {
74
+ if (mrna.type !== 'mRNA') {
75
+ currentRow += 1
76
+ continue
77
+ }
78
+ const { children: childrenOfmRNA, min } = mrna
79
+ if (!childrenOfmRNA) {
80
+ continue
81
+ }
82
+ for (const [, cds] of childrenOfmRNA) {
83
+ if (cds.type !== 'CDS') {
84
+ continue
85
+ }
86
+ const minX =
87
+ (lgv.bpToPx({
88
+ refName,
89
+ coord: min,
90
+ regionNumber: displayedRegionIndex,
91
+ })?.offsetPx ?? 0) - offsetPx
92
+ const widthPx = mrna.length / bpPerPx
93
+ const startPx = reversed ? minX - widthPx : minX
94
+ const height =
95
+ Math.round((currentRow + 1 / 2) * rowHeight) + row * rowHeight
96
+ ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
97
+ ctx.beginPath()
98
+ ctx.moveTo(startPx, height)
99
+ ctx.lineTo(startPx + widthPx, height)
100
+ ctx.stroke()
101
+ currentRow += 1
102
+ }
103
+ }
104
+
105
+ // Draw exon and CDS for each mRNA
106
+ currentRow = 0
107
+ for (const [, child] of children) {
108
+ if (child.type !== 'mRNA') {
109
+ boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
110
+ currentRow += 1
111
+ continue
112
+ }
113
+ for (const cdsRow of child.cdsLocations) {
114
+ const { _id, children: childrenOfmRNA } = child
115
+ if (!childrenOfmRNA) {
116
+ continue
117
+ }
118
+ for (const [, exon] of childrenOfmRNA) {
119
+ if (exon.type !== 'exon') {
120
+ continue
121
+ }
122
+ const minX =
123
+ (lgv.bpToPx({
124
+ refName,
125
+ coord: exon.min,
126
+ regionNumber: displayedRegionIndex,
127
+ })?.offsetPx ?? 0) - offsetPx
128
+ const widthPx = exon.length / bpPerPx
129
+ const startPx = reversed ? minX - widthPx : minX
130
+
131
+ const top = (row + currentRow) * rowHeight
132
+ const exonTop = top + (rowHeight - exonHeight) / 2
133
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
134
+ ctx.fillRect(startPx, exonTop, widthPx, exonHeight)
135
+ if (widthPx > 2) {
136
+ ctx.clearRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
137
+ ctx.fillStyle =
138
+ apolloSelectedFeature && exon._id === apolloSelectedFeature._id
139
+ ? 'rgb(0,0,0)'
140
+ : 'rgb(211,211,211)'
141
+ ctx.fillRect(startPx + 1, exonTop + 1, widthPx - 2, exonHeight - 2)
142
+ if (forwardFill && backwardFill && strand) {
143
+ const reversal = reversed ? -1 : 1
144
+ const [topFill, bottomFill] =
145
+ strand * reversal === 1
146
+ ? [forwardFill, backwardFill]
147
+ : [backwardFill, forwardFill]
148
+ ctx.fillStyle = topFill
149
+ ctx.fillRect(
150
+ startPx + 1,
151
+ exonTop + 1,
152
+ widthPx - 2,
153
+ (exonHeight - 2) / 2,
154
+ )
155
+ ctx.fillStyle = bottomFill
156
+ ctx.fillRect(
157
+ startPx + 1,
158
+ exonTop + 1 + (exonHeight - 2) / 2,
159
+ widthPx - 2,
160
+ (exonHeight - 2) / 2,
161
+ )
162
+ }
163
+ }
164
+ }
165
+ for (const cds of cdsRow) {
166
+ const cdsWidthPx = (cds.max - cds.min) / bpPerPx
167
+ const minX =
168
+ (lgv.bpToPx({
169
+ refName,
170
+ coord: cds.min,
171
+ regionNumber: displayedRegionIndex,
172
+ })?.offsetPx ?? 0) - offsetPx
173
+ const cdsStartPx = reversed ? minX - cdsWidthPx : minX
174
+ ctx.fillStyle = theme?.palette.text.primary ?? 'black'
175
+ const cdsTop =
176
+ (row + currentRow) * rowHeight + (rowHeight - cdsHeight) / 2
177
+ ctx.fillRect(cdsStartPx, cdsTop, cdsWidthPx, cdsHeight)
178
+ if (cdsWidthPx > 2) {
179
+ ctx.clearRect(
180
+ cdsStartPx + 1,
181
+ cdsTop + 1,
182
+ cdsWidthPx - 2,
183
+ cdsHeight - 2,
184
+ )
185
+ const frame = getFrame(cds.min, cds.max, child.strand ?? 1, cds.phase)
186
+ const frameColor = theme?.palette.framesCDS.at(frame)?.main
187
+ const cdsColorCode = frameColor ?? 'rgb(171,71,188)'
188
+ ctx.fillStyle =
189
+ apolloSelectedFeature && _id === apolloSelectedFeature._id
190
+ ? 'rgb(0,0,0)'
191
+ : cdsColorCode
192
+ ctx.fillStyle = cdsColorCode
193
+ ctx.fillRect(
194
+ cdsStartPx + 1,
195
+ cdsTop + 1,
196
+ cdsWidthPx - 2,
197
+ cdsHeight - 2,
198
+ )
199
+ if (forwardFill && backwardFill && strand) {
200
+ const reversal = reversed ? -1 : 1
201
+ const [topFill, bottomFill] =
202
+ strand * reversal === 1
203
+ ? [forwardFill, backwardFill]
204
+ : [backwardFill, forwardFill]
205
+ ctx.fillStyle = topFill
206
+ ctx.fillRect(
207
+ cdsStartPx + 1,
208
+ cdsTop + 1,
209
+ cdsWidthPx - 2,
210
+ (cdsHeight - 2) / 2,
211
+ )
212
+ ctx.fillStyle = bottomFill
213
+ ctx.fillRect(
214
+ cdsStartPx + 1,
215
+ cdsTop + (cdsHeight - 2) / 2,
216
+ cdsWidthPx - 2,
217
+ (cdsHeight - 2) / 2,
218
+ )
219
+ }
220
+ }
221
+ }
222
+ currentRow += 1
223
+ }
224
+ }
225
+ }
226
+
227
+ function drawDragPreview(
228
+ stateModel: LinearApolloDisplay,
229
+ overlayCtx: CanvasRenderingContext2D,
230
+ ) {
231
+ const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel
232
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
233
+ if (!apolloDragging) {
234
+ return
235
+ }
236
+ const { current, edge, feature, start } = apolloDragging
237
+
238
+ const row = Math.floor(start.y / apolloRowHeight)
239
+ const region = displayedRegions[start.regionNumber]
240
+ const rowCount = 1
241
+ const featureEdgeBp = region.reversed
242
+ ? region.end - feature[edge]
243
+ : feature[edge] - region.start
244
+ const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
245
+ const rectX = Math.min(current.x, featureEdgePx)
246
+ const rectY = row * apolloRowHeight
247
+ const rectWidth = Math.abs(current.x - featureEdgePx)
248
+ const rectHeight = apolloRowHeight * rowCount
249
+ overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
250
+ overlayCtx.setLineDash([6])
251
+ overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
252
+ overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
253
+ overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
254
+ }
255
+
256
+ function drawHover(
257
+ stateModel: LinearApolloDisplay,
258
+ ctx: CanvasRenderingContext2D,
259
+ ) {
260
+ const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
261
+ if (!apolloHover) {
262
+ return
263
+ }
264
+ const { feature } = apolloHover
265
+ const position = stateModel.getFeatureLayoutPosition(feature)
266
+ if (!position) {
267
+ return
268
+ }
269
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
270
+ const { featureRow, layoutIndex, layoutRow } = position
271
+ const displayedRegion = displayedRegions[layoutIndex]
272
+ const { refName, reversed } = displayedRegion
273
+ const { length, max, min } = feature
274
+ const startPx =
275
+ (lgv.bpToPx({
276
+ refName,
277
+ coord: reversed ? max : min,
278
+ regionNumber: layoutIndex,
279
+ })?.offsetPx ?? 0) - offsetPx
280
+ const row = layoutRow + featureRow
281
+ const top = row * apolloRowHeight
282
+ const widthPx = length / bpPerPx
283
+ ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)'
284
+ ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature))
285
+ }
286
+
287
+ function getFeatureFromLayout(
288
+ feature: AnnotationFeature,
289
+ bp: number,
290
+ row: number,
291
+ ): AnnotationFeature | undefined {
292
+ const featureInThisRow: AnnotationFeature[] = featuresForRow(feature)[row]
293
+ for (const f of featureInThisRow) {
294
+ if (bp >= f.min && bp <= f.max && f.parent) {
295
+ return f
296
+ }
297
+ }
298
+ return feature
299
+ }
300
+
301
+ function getRowCount(feature: AnnotationFeature, _bpPerPx?: number): number {
302
+ const { children, type } = feature
303
+ if (!children) {
304
+ return 1
305
+ }
306
+ let rowCount = 0
307
+ if (type === 'mRNA') {
308
+ for (const [, child] of children) {
309
+ if (child.type === 'CDS') {
310
+ rowCount += 1
311
+ }
312
+ }
313
+ return rowCount
314
+ }
315
+ for (const [, child] of children) {
316
+ rowCount += getRowCount(child)
317
+ }
318
+ return rowCount
319
+ }
320
+
321
+ /**
322
+ * A list of all the subfeatures for each row for a given feature, as well as
323
+ * the feature itself.
324
+ * If the row contains an mRNA, the order is CDS -\> exon -\> mRNA -\> gene
325
+ * If the row does not contain an mRNA, the order is subfeature -\> gene
326
+ */
327
+ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
328
+ if (feature.type !== 'gene') {
329
+ throw new Error('Top level feature for GeneGlyph must have type "gene"')
330
+ }
331
+ const { children } = feature
332
+ if (!children) {
333
+ return [[feature]]
334
+ }
335
+ const features: AnnotationFeature[][] = []
336
+ for (const [, child] of children) {
337
+ if (child.type !== 'mRNA') {
338
+ features.push([child, feature])
339
+ continue
340
+ }
341
+ if (!child.children) {
342
+ continue
343
+ }
344
+ const cdss: AnnotationFeature[] = []
345
+ const exons: AnnotationFeature[] = []
346
+ for (const [, grandchild] of child.children) {
347
+ if (grandchild.type === 'CDS') {
348
+ cdss.push(grandchild)
349
+ } else if (grandchild.type === 'exon') {
350
+ exons.push(grandchild)
351
+ }
352
+ }
353
+ for (const cds of cdss) {
354
+ features.push([cds, ...exons, child, feature])
355
+ }
356
+ }
357
+ return features
358
+ }
359
+
360
+ function getRowForFeature(
361
+ feature: AnnotationFeature,
362
+ childFeature: AnnotationFeature,
363
+ ) {
364
+ const rows = featuresForRow(feature)
365
+ for (const [idx, row] of rows.entries()) {
366
+ if (row.some((feature) => feature._id === childFeature._id)) {
367
+ return idx
368
+ }
369
+ }
370
+ return
371
+ }
372
+
373
+ function onMouseDown(
374
+ stateModel: LinearApolloDisplay,
375
+ currentMousePosition: MousePositionWithFeatureAndGlyph,
376
+ event: CanvasMouseEvent,
377
+ ) {
378
+ const { featureAndGlyphUnderMouse } = currentMousePosition
379
+ // swallow the mouseDown if we are on the edge of the feature so that we
380
+ // don't start dragging the view if we try to drag the feature edge
381
+ const { feature } = featureAndGlyphUnderMouse
382
+ const draggableFeature = getDraggableFeatureInfo(
383
+ currentMousePosition,
384
+ feature,
385
+ stateModel,
386
+ )
387
+ if (draggableFeature) {
388
+ event.stopPropagation()
389
+ stateModel.startDrag(
390
+ currentMousePosition,
391
+ draggableFeature.feature,
392
+ draggableFeature.edge,
393
+ )
394
+ }
395
+ }
396
+
397
+ function onMouseMove(
398
+ stateModel: LinearApolloDisplay,
399
+ mousePosition: MousePosition,
400
+ ) {
401
+ if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
402
+ const { featureAndGlyphUnderMouse } = mousePosition
403
+ stateModel.setApolloHover(featureAndGlyphUnderMouse)
404
+ const { feature } = featureAndGlyphUnderMouse
405
+ const draggableFeature = getDraggableFeatureInfo(
406
+ mousePosition,
407
+ feature,
408
+ stateModel,
409
+ )
410
+ if (draggableFeature) {
411
+ stateModel.setCursor('col-resize')
412
+ return
413
+ }
414
+ }
415
+ stateModel.setCursor()
416
+ }
417
+
418
+ function onMouseUp(
419
+ stateModel: LinearApolloDisplay,
420
+ mousePosition: MousePosition,
421
+ ) {
422
+ if (stateModel.apolloDragging) {
423
+ return
424
+ }
425
+ const { featureAndGlyphUnderMouse } = mousePosition
426
+ if (featureAndGlyphUnderMouse?.feature) {
427
+ stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature)
428
+ }
429
+ }
430
+
431
+ function getDraggableFeatureInfo(
432
+ mousePosition: MousePosition,
433
+ feature: AnnotationFeature,
434
+ stateModel: LinearApolloDisplay,
435
+ ): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
436
+ if (feature.type === 'gene' || feature.type === 'mRNA') {
437
+ return
438
+ }
439
+ const { bp, refName, regionNumber, x } = mousePosition
440
+ const { lgv } = stateModel
441
+ const { offsetPx } = lgv
442
+
443
+ const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber })
444
+ const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber })
445
+ if (minPxInfo === undefined || maxPxInfo === undefined) {
446
+ return
447
+ }
448
+ const minPx = minPxInfo.offsetPx - offsetPx
449
+ const maxPx = maxPxInfo.offsetPx - offsetPx
450
+ if (Math.abs(maxPx - minPx) < 8) {
451
+ return
452
+ }
453
+ if (Math.abs(minPx - x) < 4) {
454
+ return { feature, edge: 'min' }
455
+ }
456
+ if (Math.abs(maxPx - x) < 4) {
457
+ return { feature, edge: 'max' }
458
+ }
459
+ if (feature.type === 'CDS') {
460
+ const mRNA = feature.parent
461
+ if (!mRNA?.children) {
462
+ return
463
+ }
464
+ const exonChildren = [...mRNA.children.values()].filter(
465
+ (child) => child.type === 'exon',
466
+ )
467
+ const overlappingExon = exonChildren.find((child) => {
468
+ const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
469
+ return start !== undefined && end !== undefined
470
+ })
471
+ if (!overlappingExon) {
472
+ return
473
+ }
474
+ const minPxInfo = lgv.bpToPx({
475
+ refName,
476
+ coord: overlappingExon.min,
477
+ regionNumber,
478
+ })
479
+ const maxPxInfo = lgv.bpToPx({
480
+ refName,
481
+ coord: overlappingExon.max,
482
+ regionNumber,
483
+ })
484
+ if (minPxInfo === undefined || maxPxInfo === undefined) {
485
+ return
486
+ }
487
+ const minPx = minPxInfo.offsetPx - offsetPx
488
+ const maxPx = maxPxInfo.offsetPx - offsetPx
489
+ if (Math.abs(maxPx - minPx) < 8) {
490
+ return
491
+ }
492
+ if (Math.abs(minPx - x) < 4) {
493
+ return { feature: overlappingExon, edge: 'min' }
494
+ }
495
+ if (Math.abs(maxPx - x) < 4) {
496
+ return { feature: overlappingExon, edge: 'max' }
497
+ }
498
+ }
499
+ return
500
+ }
501
+
502
+ // False positive here, none of these functions use "this"
503
+ /* eslint-disable @typescript-eslint/unbound-method */
504
+ const { drawTooltip, getContextMenuItems, onMouseLeave } = boxGlyph
505
+ /* eslint-enable @typescript-eslint/unbound-method */
506
+
507
+ export const geneGlyph: Glyph = {
508
+ draw,
509
+ drawDragPreview,
510
+ drawHover,
511
+ drawTooltip,
512
+ getContextMenuItems,
513
+ getFeatureFromLayout,
514
+ getRowCount,
515
+ getRowForFeature,
516
+ onMouseDown,
517
+ onMouseLeave,
518
+ onMouseMove,
519
+ onMouseUp,
520
+ }