@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
@@ -1,1204 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
4
- /* eslint-disable @typescript-eslint/unbound-method */
5
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
6
- import { AnnotationFeatureI } from '@apollo-annotation/mst'
7
- import {
8
- DiscontinuousLocationEndChange,
9
- DiscontinuousLocationStartChange,
10
- LocationEndChange,
11
- LocationStartChange,
12
- } from '@apollo-annotation/shared'
13
- import { alpha } from '@mui/material'
14
-
15
- import { LinearApolloDisplay } from '../stateModel'
16
- import {
17
- CDSDiscontinuousLocation,
18
- MousePosition,
19
- } from '../stateModel/mouseEvents'
20
- import { frameColors, getFrame } from '../stateModel/rendering'
21
- import { CanvasMouseEvent } from '../types'
22
- import { Glyph } from './Glyph'
23
-
24
- type LocationChange =
25
- | DiscontinuousLocationEndChange
26
- | DiscontinuousLocationStartChange
27
- | LocationEndChange
28
- | LocationStartChange
29
-
30
- let forwardFill: CanvasPattern | null = null
31
- let backwardFill: CanvasPattern | null = null
32
- if ('document' in window) {
33
- for (const direction of ['forward', 'backward']) {
34
- const canvas = document.createElement('canvas')
35
- const canvasSize = 10
36
- canvas.width = canvas.height = canvasSize
37
- const ctx = canvas.getContext('2d')
38
- if (ctx) {
39
- const stripeColor1 = 'rgba(0,0,0,0)'
40
- const stripeColor2 = 'rgba(255,255,255,0.25)'
41
- const gradient =
42
- direction === 'forward'
43
- ? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
44
- : ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
45
- gradient.addColorStop(0, stripeColor1)
46
- gradient.addColorStop(0.25, stripeColor1)
47
- gradient.addColorStop(0.25, stripeColor2)
48
- gradient.addColorStop(0.5, stripeColor2)
49
- gradient.addColorStop(0.5, stripeColor1)
50
- gradient.addColorStop(0.75, stripeColor1)
51
- gradient.addColorStop(0.75, stripeColor2)
52
- gradient.addColorStop(1, stripeColor2)
53
- ctx.fillStyle = gradient
54
- ctx.fillRect(0, 0, 10, 10)
55
- if (direction === 'forward') {
56
- forwardFill = ctx.createPattern(canvas, 'repeat')
57
- } else {
58
- backwardFill = ctx.createPattern(canvas, 'repeat')
59
- }
60
- }
61
- }
62
- }
63
-
64
- interface AnnotationFeature {
65
- parent?: AnnotationFeatureI
66
- start?: number
67
- end?: number
68
- phase?: 0 | 1 | 2
69
- annotationFeature: AnnotationFeatureI
70
- }
71
-
72
- interface CDSFeatures {
73
- parent: AnnotationFeatureI
74
- cds: AnnotationFeatureI
75
- }
76
-
77
- interface ExonCDSRelation {
78
- exon: AnnotationFeatureI
79
- cdsDL?: CDSDiscontinuousLocation
80
- }
81
-
82
- export class CanonicalGeneGlyph extends Glyph {
83
- featuresForRow(feature: AnnotationFeatureI): AnnotationFeature[][] {
84
- const cdsFeatures: CDSFeatures[] = []
85
- for (const [, child] of feature.children ?? new Map()) {
86
- for (const [, annotationFeature] of child.children ?? new Map()) {
87
- if (annotationFeature.type === 'CDS') {
88
- cdsFeatures.push({
89
- parent: child,
90
- cds: annotationFeature,
91
- })
92
- }
93
- }
94
- }
95
-
96
- const features: AnnotationFeature[][] = []
97
- for (const f of cdsFeatures) {
98
- const childFeatures: AnnotationFeature[] = []
99
- for (const [, cf] of f.parent.children ?? new Map()) {
100
- if (cf.type === 'CDS' && cf._id !== f.cds._id) {
101
- continue
102
- }
103
- if (cf.discontinuousLocations && cf.discontinuousLocations.length > 0) {
104
- for (const dl of cf.discontinuousLocations) {
105
- childFeatures.push({
106
- annotationFeature: cf,
107
- parent: f.parent,
108
- start: dl.start,
109
- end: dl.end,
110
- phase: dl.phase,
111
- })
112
- }
113
- } else {
114
- childFeatures.push({
115
- annotationFeature: cf,
116
- parent: f.parent,
117
- })
118
- }
119
- }
120
- childFeatures.push({
121
- annotationFeature: f.parent,
122
- })
123
- features.push(childFeatures)
124
- }
125
-
126
- return features
127
- }
128
-
129
- getRowCount(feature: AnnotationFeatureI, _bpPerPx?: number): number {
130
- let cdsCount = 0
131
- for (const [, child] of feature.children ?? new Map()) {
132
- for (const [, grandchild] of child.children ?? new Map()) {
133
- if (grandchild.type === 'CDS') {
134
- cdsCount += 1
135
- }
136
- }
137
- }
138
- return cdsCount
139
- }
140
-
141
- draw(
142
- stateModel: LinearApolloDisplay,
143
- ctx: CanvasRenderingContext2D,
144
- feature: AnnotationFeatureI,
145
- xOffset: number,
146
- row: number,
147
- reversed: boolean,
148
- ): void {
149
- const { apolloRowHeight, lgv, session, theme } = stateModel
150
- const { bpPerPx } = lgv
151
- const rowHeight = apolloRowHeight
152
- const utrHeight = Math.round(0.6 * rowHeight)
153
- const cdsHeight = Math.round(0.9 * rowHeight)
154
- const { _id, children, min, strand } = feature
155
- const { apolloSelectedFeature } = session
156
- let currentCDS = 0
157
- for (const [, mrna] of children ?? new Map()) {
158
- if (mrna.type !== 'mRNA') {
159
- continue
160
- }
161
- for (const [, cds] of mrna.children ?? new Map()) {
162
- if (cds.type !== 'CDS') {
163
- continue
164
- }
165
- const offsetPx = (mrna.start - min) / bpPerPx
166
- const widthPx = mrna.length / bpPerPx
167
- const startPx = reversed
168
- ? xOffset - offsetPx - widthPx
169
- : xOffset + offsetPx
170
- const height =
171
- Math.round((currentCDS + 1 / 2) * rowHeight) + row * rowHeight
172
- ctx.strokeStyle = theme?.palette.text.primary ?? 'black'
173
- ctx.beginPath()
174
- ctx.moveTo(startPx, height)
175
- ctx.lineTo(startPx + widthPx, height)
176
- ctx.stroke()
177
- currentCDS += 1
178
- }
179
- }
180
- currentCDS = 0
181
- for (const [, mrna] of children ?? new Map()) {
182
- if (mrna.type !== 'mRNA') {
183
- continue
184
- }
185
- const cdsCount = [...(mrna.children ?? [])].filter(
186
- ([, exonOrCDS]) => exonOrCDS.type === 'CDS',
187
- ).length
188
- for (let count = 0; count < cdsCount; count++) {
189
- for (const [, exon] of mrna.children ?? new Map()) {
190
- if (exon.type !== 'exon') {
191
- continue
192
- }
193
- const offsetPx = (exon.start - min) / bpPerPx
194
- const widthPx = exon.length / bpPerPx
195
- const startPx = reversed
196
- ? xOffset - offsetPx - widthPx
197
- : xOffset + offsetPx
198
- const top = (row + currentCDS) * rowHeight
199
- const utrTop = top + (rowHeight - utrHeight) / 2
200
- ctx.fillStyle = theme?.palette.text.primary ?? 'black'
201
- ctx.fillRect(startPx, utrTop, widthPx, utrHeight)
202
- if (widthPx > 2) {
203
- ctx.clearRect(startPx + 1, utrTop + 1, widthPx - 2, utrHeight - 2)
204
- ctx.fillStyle =
205
- apolloSelectedFeature && exon._id === apolloSelectedFeature._id
206
- ? 'rgb(0,0,0)'
207
- : 'rgb(211,211,211)'
208
- ctx.fillRect(startPx + 1, utrTop + 1, widthPx - 2, utrHeight - 2)
209
- if (forwardFill && backwardFill && strand) {
210
- const reversal = reversed ? -1 : 1
211
- const [topFill, bottomFill] =
212
- strand * reversal === 1
213
- ? [forwardFill, backwardFill]
214
- : [backwardFill, forwardFill]
215
- ctx.fillStyle = topFill
216
- ctx.fillRect(
217
- startPx + 1,
218
- utrTop + 1,
219
- widthPx - 2,
220
- (utrHeight - 2) / 2,
221
- )
222
- ctx.fillStyle = bottomFill
223
- ctx.fillRect(
224
- startPx + 1,
225
- utrTop + 1 + (utrHeight - 2) / 2,
226
- widthPx - 2,
227
- (utrHeight - 2) / 2,
228
- )
229
- }
230
- }
231
- }
232
- currentCDS += 1
233
- }
234
- }
235
- currentCDS = 0
236
- for (const [, mrna] of children ?? new Map()) {
237
- if (mrna.type !== 'mRNA') {
238
- continue
239
- }
240
- for (const [, cds] of mrna.children ?? new Map()) {
241
- if (cds.type !== 'CDS') {
242
- continue
243
- }
244
- if (cds.discontinuousLocations) {
245
- for (const cdsLocation of cds.discontinuousLocations) {
246
- const offsetPx = (cdsLocation.start - min) / bpPerPx
247
- const widthPx = (cdsLocation.end - cdsLocation.start) / bpPerPx
248
- const startPx = reversed
249
- ? xOffset - offsetPx - widthPx
250
- : xOffset + offsetPx
251
- ctx.fillStyle = theme?.palette.text.primary ?? 'black'
252
- const top = (row + currentCDS) * rowHeight
253
- const cdsTop = top + (rowHeight - cdsHeight) / 2
254
- ctx.fillRect(startPx, cdsTop, widthPx, cdsHeight)
255
- if (widthPx > 2) {
256
- ctx.clearRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
257
- const frame = getFrame(
258
- cdsLocation.start,
259
- cdsLocation.end,
260
- cdsLocation.strand,
261
- cdsLocation.phase,
262
- )
263
- const cdsColorCode = frameColors.at(frame) ?? 'rgb(171,71,188)'
264
- ctx.fillStyle =
265
- apolloSelectedFeature && cds._id === apolloSelectedFeature._id
266
- ? 'rgb(0,0,0)'
267
- : cdsColorCode
268
- ctx.fillRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
269
- if (forwardFill && backwardFill && strand) {
270
- const reversal = reversed ? -1 : 1
271
- const [topFill, bottomFill] =
272
- strand * reversal === 1
273
- ? [forwardFill, backwardFill]
274
- : [backwardFill, forwardFill]
275
- ctx.fillStyle = topFill
276
- ctx.fillRect(
277
- startPx + 1,
278
- cdsTop + 1,
279
- widthPx - 2,
280
- (cdsHeight - 2) / 2,
281
- )
282
- ctx.fillStyle = bottomFill
283
- ctx.fillRect(
284
- startPx + 1,
285
- cdsTop + (cdsHeight - 2) / 2,
286
- widthPx - 2,
287
- (cdsHeight - 2) / 2,
288
- )
289
- }
290
- }
291
- }
292
- }
293
- currentCDS += 1
294
- }
295
- }
296
- if (apolloSelectedFeature) {
297
- if (_id === apolloSelectedFeature._id) {
298
- const widthPx = feature.length / bpPerPx
299
- const startPx = reversed ? xOffset - widthPx : xOffset
300
- const top = row * rowHeight
301
- const height = this.getRowCount(feature) * rowHeight
302
- ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,0.08)'
303
- ctx.fillRect(startPx, top, widthPx, height)
304
- } else {
305
- let featureEntry: AnnotationFeatureI | undefined
306
- let featureRow: number | undefined
307
- let i = 0
308
- for (const [, f] of children ?? new Map()) {
309
- if (f._id === apolloSelectedFeature._id) {
310
- featureEntry = f
311
- featureRow = i
312
- }
313
- i++
314
- }
315
-
316
- if (featureEntry === undefined || featureRow === undefined) {
317
- return
318
- }
319
- const cdsCount = this.cdsCount(featureEntry)
320
- let height = rowHeight
321
- if (cdsCount > 1) {
322
- height = height * cdsCount
323
- }
324
- const widthPx = featureEntry.length / bpPerPx
325
- const offsetPx = (featureEntry.start - min) / bpPerPx
326
- const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx
327
- const top = (row + featureRow) * rowHeight
328
- ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,08)'
329
- ctx.fillRect(startPx, top, widthPx, height)
330
- }
331
- }
332
- }
333
-
334
- // CDS count with discontinuous locations
335
- cdsCount(feature?: AnnotationFeatureI) {
336
- let cdsCount = 0
337
- for (const [, cf] of feature?.children ?? new Map()) {
338
- if (
339
- cf.type === 'CDS' &&
340
- cf.discontinuousLocations &&
341
- cf.discontinuousLocations.length > 0
342
- ) {
343
- cdsCount++
344
- }
345
- }
346
- return cdsCount
347
- }
348
-
349
- drawHover(
350
- stateModel: LinearApolloDisplay,
351
- ctx: CanvasRenderingContext2D,
352
- rowNum: number,
353
- xOffset: number,
354
- reversed: boolean,
355
- ) {
356
- const { apolloHover } = stateModel
357
- if (!apolloHover) {
358
- return
359
- }
360
- const { feature } = apolloHover
361
- if (!feature) {
362
- return
363
- }
364
-
365
- if (
366
- feature.discontinuousLocations &&
367
- feature.discontinuousLocations.length > 0
368
- ) {
369
- for (const dl of feature.discontinuousLocations) {
370
- this.drawShadeForFeature(
371
- stateModel,
372
- ctx,
373
- dl.start,
374
- dl.end,
375
- dl.end - dl.start,
376
- )
377
- }
378
- } else {
379
- this.drawShadeForFeature(
380
- stateModel,
381
- ctx,
382
- feature.start,
383
- feature.end,
384
- feature.length,
385
- rowNum,
386
- xOffset,
387
- reversed,
388
- )
389
- }
390
- }
391
-
392
- drawShadeForFeature(
393
- stateModel: LinearApolloDisplay,
394
- ctx: CanvasRenderingContext2D,
395
- start: number,
396
- end: number,
397
- length: number,
398
- rowNum?: number,
399
- xOffset?: number,
400
- reversed?: boolean,
401
- ) {
402
- const { apolloHover, apolloRowHeight, displayedRegions, lgv, theme } =
403
- stateModel
404
- const { bpPerPx, bpToPx, offsetPx } = lgv
405
-
406
- if (!apolloHover) {
407
- return
408
- }
409
- const { feature, topLevelFeature } = apolloHover
410
-
411
- if (!feature || !topLevelFeature) {
412
- return
413
- }
414
-
415
- let featureEntry: AnnotationFeatureI | undefined
416
- let childFeature: AnnotationFeatureI | undefined
417
- let featureRow: number | undefined
418
- let i = 0
419
- for (const [, f] of topLevelFeature.children ?? new Map()) {
420
- if (f._id === feature._id) {
421
- featureEntry = f
422
- featureRow = i
423
- }
424
- for (const [, cf] of f.children ?? new Map()) {
425
- if (cf._id === feature._id) {
426
- childFeature = cf
427
- featureEntry = f
428
- featureRow = i
429
- }
430
- }
431
- i++
432
- }
433
-
434
- const cdsCount = this.cdsCount(featureEntry)
435
-
436
- if (cdsCount > 1 && rowNum && xOffset) {
437
- if (featureEntry === undefined || featureRow === undefined) {
438
- return
439
- }
440
- const widthPx = childFeature
441
- ? childFeature.length / bpPerPx
442
- : featureEntry.length / bpPerPx
443
- const offsetPx = childFeature
444
- ? (childFeature.start - feature.min) / bpPerPx
445
- : (featureEntry.start - feature.min) / bpPerPx
446
- const startPx = reversed ? xOffset - widthPx : xOffset + offsetPx
447
- const top = (rowNum + featureRow) * apolloRowHeight
448
- ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)'
449
- ctx.fillRect(startPx, top, widthPx, apolloRowHeight * cdsCount)
450
- } else {
451
- const { mousePosition } = apolloHover
452
- if (!mousePosition) {
453
- return
454
- }
455
- const rowHeight = apolloRowHeight
456
- const { regionNumber, y } = mousePosition
457
- const rowNumber = Math.floor(y / rowHeight)
458
-
459
- const displayedRegion = displayedRegions[regionNumber]
460
- const { refName, reversed } = displayedRegion
461
- const startPx =
462
- (bpToPx({
463
- refName,
464
- coord: reversed ? end : start,
465
- regionNumber,
466
- })?.offsetPx ?? 0) - offsetPx
467
- const top = rowNumber * rowHeight
468
- const widthPx = length / bpPerPx
469
- ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
470
- ctx.fillRect(startPx, top, widthPx, rowHeight)
471
- }
472
- }
473
-
474
- drawDragPreview(
475
- stateModel: LinearApolloDisplay,
476
- overlayCtx: CanvasRenderingContext2D,
477
- ) {
478
- const { apolloDragging, apolloRowHeight, displayedRegions, lgv, theme } =
479
- stateModel
480
- const { bpPerPx, offsetPx } = lgv
481
- if (!apolloDragging) {
482
- return
483
- }
484
- const {
485
- discontinuousLocation,
486
- feature,
487
- glyph,
488
- mousePosition: startingMousePosition,
489
- } = apolloDragging.start
490
- if (!feature) {
491
- throw new Error('no feature for drag preview??')
492
- }
493
- if (glyph !== this) {
494
- throw new Error('drawDragPreview() called on wrong glyph?')
495
- }
496
- const { mousePosition: currentMousePosition } = apolloDragging.current
497
- const edge = this.isMouseOnFeatureEdge(
498
- startingMousePosition,
499
- feature,
500
- stateModel,
501
- )
502
- if (!edge) {
503
- return
504
- }
505
-
506
- const row = Math.floor(startingMousePosition.y / apolloRowHeight)
507
- const region = displayedRegions[startingMousePosition.regionNumber]
508
- const rowCount = 1
509
-
510
- let featureEdgeBp
511
- if (discontinuousLocation) {
512
- featureEdgeBp = region.reversed
513
- ? region.end - discontinuousLocation[edge]
514
- : discontinuousLocation[edge] - region.start
515
- } else {
516
- featureEdgeBp = region.reversed
517
- ? region.end - feature[edge]
518
- : feature[edge] - region.start
519
- }
520
- const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
521
-
522
- const rectX = Math.min(currentMousePosition.x, featureEdgePx)
523
- const rectY = row * apolloRowHeight
524
- const rectWidth = Math.abs(currentMousePosition.x - featureEdgePx)
525
- const rectHeight = apolloRowHeight * rowCount
526
-
527
- overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
528
- overlayCtx.setLineDash([6])
529
- overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
530
- overlayCtx.fillStyle = alpha(
531
- theme?.palette.info.main ?? 'rgb(255,0,0)',
532
- 0.2,
533
- )
534
- overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
535
- }
536
-
537
- /**
538
- * Check If the mouse position is on the edge of the selected feature
539
- */
540
- isMouseOnFeatureEdge(
541
- mousePosition: MousePosition,
542
- feature: AnnotationFeatureI,
543
- stateModel: LinearApolloDisplay,
544
- topLevelFeature?: AnnotationFeatureI,
545
- ) {
546
- if (!mousePosition) {
547
- return
548
- }
549
-
550
- const { bp, refName, regionNumber, x } = mousePosition
551
- const { lgv } = stateModel
552
- const { bpToPx, offsetPx } = lgv
553
- let startPxInfo
554
- let endPxInfo
555
- if (
556
- feature.discontinuousLocations &&
557
- feature.discontinuousLocations.length > 0
558
- ) {
559
- let discontinuousLocation
560
- for (const dl of feature.discontinuousLocations) {
561
- if (bp >= dl.start && bp <= dl.end) {
562
- discontinuousLocation = dl
563
- break
564
- }
565
- }
566
- if (!discontinuousLocation) {
567
- return
568
- }
569
- startPxInfo = bpToPx({
570
- refName,
571
- coord: discontinuousLocation.start,
572
- regionNumber,
573
- })
574
- endPxInfo = bpToPx({
575
- refName,
576
- coord: discontinuousLocation.end,
577
- regionNumber,
578
- })
579
- } else {
580
- startPxInfo = bpToPx({
581
- refName,
582
- coord: feature.start,
583
- regionNumber,
584
- })
585
- endPxInfo = bpToPx({ refName, coord: feature.end, regionNumber })
586
- }
587
-
588
- if (startPxInfo !== undefined && endPxInfo !== undefined) {
589
- const startPx = startPxInfo.offsetPx - offsetPx
590
- const endPx = endPxInfo.offsetPx - offsetPx
591
- if (Math.abs(endPx - startPx) < 8) {
592
- return
593
- }
594
- const parentFeature = this.getParentFeature(feature, topLevelFeature)
595
- // Limit dragging till parent feature end
596
- if (parentFeature) {
597
- if (parentFeature.type === 'gene') {
598
- return
599
- }
600
- if (feature.start <= parentFeature.start && Math.abs(startPx - x) < 4) {
601
- return
602
- }
603
- if (feature.end >= parentFeature.end && Math.abs(endPx - x) < 4) {
604
- return
605
- }
606
- }
607
- if (Math.abs(startPx - x) < 4) {
608
- return 'start'
609
- }
610
- if (Math.abs(endPx - x) < 4) {
611
- return 'end'
612
- }
613
- }
614
- return
615
- }
616
-
617
- onMouseMove(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
618
- const { feature, mousePosition, topLevelFeature } =
619
- stateModel.getFeatureAndGlyphUnderMouse(event)
620
- if (stateModel.apolloDragging) {
621
- stateModel.setCursor('col-resize')
622
- return
623
- }
624
- if (feature && mousePosition) {
625
- const edge = this.isMouseOnFeatureEdge(
626
- mousePosition,
627
- feature,
628
- stateModel,
629
- topLevelFeature,
630
- )
631
- if (edge) {
632
- stateModel.setCursor('col-resize')
633
- } else {
634
- stateModel.setCursor()
635
- }
636
- }
637
- }
638
-
639
- onMouseDown(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
640
- // swallow the mouseDown if we are on the edge of the feature
641
- const { feature, mousePosition } =
642
- stateModel.getFeatureAndGlyphUnderMouse(event)
643
- if (feature && mousePosition) {
644
- const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
645
- if (edge) {
646
- event.stopPropagation()
647
- }
648
- }
649
- }
650
-
651
- onMouseUp(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
652
- if (stateModel.apolloDragging ?? event.button !== 0) {
653
- return
654
- }
655
- const { feature } = stateModel.getFeatureAndGlyphUnderMouse(event)
656
- if (feature) {
657
- stateModel.setSelectedFeature(feature)
658
- }
659
- }
660
-
661
- startDrag(stateModel: LinearApolloDisplay): boolean {
662
- // only accept the drag if we are on the edge of the feature
663
- const { feature, mousePosition, topLevelFeature } =
664
- stateModel.apolloDragging?.start ?? {}
665
- const { mousePosition: currentMousePosition } =
666
- stateModel.apolloDragging?.current ?? {}
667
- if (feature && mousePosition && currentMousePosition) {
668
- const edge = this.isMouseOnFeatureEdge(
669
- mousePosition,
670
- feature,
671
- stateModel,
672
- topLevelFeature,
673
- )
674
- if (edge) {
675
- return true
676
- }
677
- }
678
- return false
679
- }
680
-
681
- adjacentExonsOfCdsDL(
682
- cdsDL: CDSDiscontinuousLocation,
683
- exonCDSRelations: ExonCDSRelation[],
684
- ) {
685
- let prevExon, nextExon, matchingExon, idx
686
- if (exonCDSRelations) {
687
- for (const [i, exonCDSRelation] of exonCDSRelations.entries()) {
688
- const dl = exonCDSRelation.cdsDL
689
- if (
690
- cdsDL.start === dl?.start &&
691
- cdsDL.end === dl.end &&
692
- cdsDL.phase === dl.phase
693
- ) {
694
- idx = i
695
- break
696
- }
697
- }
698
- if (idx !== undefined) {
699
- const { exon } = exonCDSRelations[idx]
700
- matchingExon = exon
701
- }
702
- if (idx !== undefined && idx > 0) {
703
- prevExon = exonCDSRelations[idx - 1].exon
704
- }
705
- if (idx !== undefined && idx < exonCDSRelations.length - 1) {
706
- nextExon = exonCDSRelations[idx + 1].exon
707
- }
708
- }
709
- return { prevExon, matchingExon, nextExon }
710
- }
711
-
712
- exonCDSRelation(
713
- cds?: AnnotationFeatureI,
714
- topLevelFeature?: AnnotationFeatureI,
715
- ): ExonCDSRelation[] {
716
- const exonCDSRelations: ExonCDSRelation[] = []
717
- if (!cds) {
718
- return exonCDSRelations
719
- }
720
- const parentFeature = this.getParentFeature(cds, topLevelFeature)
721
- if (!parentFeature?.children) {
722
- return exonCDSRelations
723
- }
724
- for (const [, f] of parentFeature.children) {
725
- if (f.type === 'exon') {
726
- const cdsDLForExon = this.cdsDLForExon(f, cds)
727
- exonCDSRelations.push({
728
- exon: f,
729
- cdsDL: cdsDLForExon
730
- ? {
731
- start: cdsDLForExon.start,
732
- end: cdsDLForExon.end,
733
- phase: cdsDLForExon.phase,
734
- }
735
- : undefined,
736
- })
737
- }
738
- }
739
- return exonCDSRelations
740
- }
741
-
742
- cdsDLForExon(exon: AnnotationFeatureI, cds: AnnotationFeatureI) {
743
- let discontinuousLocation
744
- if (cds.discontinuousLocations && cds.discontinuousLocations.length > 0) {
745
- for (const dl of cds.discontinuousLocations) {
746
- if (dl.start >= exon.start && dl.end <= exon.end) {
747
- discontinuousLocation = dl
748
- break
749
- }
750
- }
751
- }
752
- return discontinuousLocation
753
- }
754
-
755
- cdsDlsForExon(
756
- exon: AnnotationFeatureI,
757
- topLevelFeature?: AnnotationFeatureI,
758
- ): CDSDiscontinuousLocation[] {
759
- const dls: CDSDiscontinuousLocation[] = []
760
- const parentFeature = this.getParentFeature(exon, topLevelFeature)
761
- if (!parentFeature?.children || !topLevelFeature) {
762
- return dls
763
- }
764
- const cdsFeatures: AnnotationFeatureI[] = []
765
- for (const [, f] of parentFeature.children) {
766
- if (f.type === 'CDS') {
767
- cdsFeatures.push(f)
768
- }
769
- }
770
-
771
- for (const cds of cdsFeatures) {
772
- for (const [, f] of parentFeature.children) {
773
- if (f.type === 'exon' && f._id === exon._id) {
774
- const cdsDLForExon = this.cdsDLForExon(f, cds)
775
- if (cdsDLForExon) {
776
- dls.push(cdsDLForExon)
777
- }
778
- }
779
- }
780
- }
781
- return dls
782
- }
783
-
784
- adjacentExonsOfExon(
785
- exon: AnnotationFeatureI,
786
- topLevelFeature?: AnnotationFeatureI,
787
- ) {
788
- const parentFeature: AnnotationFeatureI = this.getParentFeature(
789
- exon,
790
- topLevelFeature,
791
- )
792
- if (!(parentFeature && parentFeature.children)) {
793
- return
794
- }
795
-
796
- let i = 0
797
- for (const [, f] of parentFeature.children) {
798
- if (f._id === exon._id) {
799
- break
800
- }
801
- i++
802
- }
803
-
804
- let prevExon, nextExon
805
- const keys = [...parentFeature.children.keys()]
806
- if (i > 0) {
807
- const f = parentFeature.children.get(keys[i - 1])
808
- if (f && f.type === 'exon') {
809
- prevExon = f
810
- }
811
- }
812
- if (i < keys.length - 1) {
813
- const f = parentFeature.children.get(keys[i + 1])
814
- if (f && f.type === 'exon') {
815
- nextExon = f
816
- }
817
- }
818
- return { prevExon, nextExon }
819
- }
820
-
821
- continueDrag(
822
- stateModel: LinearApolloDisplay,
823
- currentMousePosition: MousePosition,
824
- ): void {
825
- const {
826
- discontinuousLocation,
827
- feature,
828
- glyph,
829
- mousePosition,
830
- topLevelFeature,
831
- } = stateModel.apolloDragging?.start ?? {}
832
- if (!(currentMousePosition && mousePosition)) {
833
- return
834
- }
835
- const { bp } = currentMousePosition
836
- if (!feature || !currentMousePosition) {
837
- return
838
- }
839
- const edge = this.isMouseOnFeatureEdge(
840
- mousePosition,
841
- feature,
842
- stateModel,
843
- topLevelFeature,
844
- )
845
-
846
- if (
847
- feature.type === 'CDS' &&
848
- feature.discontinuousLocations &&
849
- feature.discontinuousLocations.length > 0 &&
850
- discontinuousLocation
851
- ) {
852
- const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
853
- const { matchingExon, nextExon, prevExon } = this.adjacentExonsOfCdsDL(
854
- discontinuousLocation,
855
- exonCDSRelations,
856
- )
857
-
858
- if (nextExon && bp >= nextExon.start - 1) {
859
- return
860
- }
861
- if (prevExon && bp <= prevExon.end + 1) {
862
- return
863
- }
864
- if (!prevExon && nextExon && matchingExon && bp < matchingExon.start) {
865
- return
866
- }
867
- if (prevExon && !nextExon && matchingExon && bp > matchingExon.end) {
868
- return
869
- }
870
-
871
- if (
872
- edge &&
873
- ((edge === 'start' && bp >= discontinuousLocation.end - 1) ||
874
- (edge === 'end' && bp <= discontinuousLocation.start + 1))
875
- ) {
876
- return
877
- }
878
- }
879
-
880
- if (feature.type !== 'CDS') {
881
- const adjacentExons = this.adjacentExonsOfExon(feature, topLevelFeature)
882
- if (adjacentExons?.nextExon && bp >= adjacentExons.nextExon.start - 1) {
883
- return
884
- }
885
- if (adjacentExons?.prevExon && bp <= adjacentExons.prevExon.end + 1) {
886
- return
887
- }
888
- const dls: CDSDiscontinuousLocation[] = this.cdsDlsForExon(
889
- feature,
890
- topLevelFeature,
891
- )
892
-
893
- if (dls && dls.length > 0) {
894
- let stopDrag
895
- for (const dl of dls) {
896
- if (
897
- edge &&
898
- ((edge === 'start' && bp >= dl.start - 1) ||
899
- (edge === 'end' && bp <= dl.end + 1))
900
- ) {
901
- stopDrag = true
902
- break
903
- }
904
- }
905
- if (stopDrag) {
906
- return
907
- }
908
- } else {
909
- if (
910
- edge &&
911
- ((edge === 'start' && bp >= feature.end - 1) ||
912
- (edge === 'end' && bp <= feature.start + 1))
913
- ) {
914
- return
915
- }
916
- }
917
- }
918
-
919
- stateModel.setDragging({
920
- start: {
921
- feature,
922
- topLevelFeature,
923
- glyph,
924
- discontinuousLocation,
925
- mousePosition,
926
- },
927
- current: {
928
- feature,
929
- topLevelFeature,
930
- glyph,
931
- mousePosition: currentMousePosition,
932
- },
933
- })
934
- }
935
-
936
- getFeatureFromLayout(
937
- feature: AnnotationFeatureI,
938
- bp: number,
939
- row: number,
940
- ): AnnotationFeatureI | undefined {
941
- const featuresForRow: AnnotationFeature[] =
942
- this.featuresForRow(feature)[row]
943
- let featureFromLayout: AnnotationFeatureI | undefined
944
-
945
- for (const f of featuresForRow) {
946
- if (
947
- f.start !== undefined &&
948
- f.end !== undefined &&
949
- f.phase !== undefined
950
- ) {
951
- if (bp >= f.start && bp <= f.end && f.parent) {
952
- featureFromLayout = f.annotationFeature
953
- }
954
- } else {
955
- if (
956
- bp >= f.annotationFeature.start &&
957
- bp <= f.annotationFeature.end &&
958
- f.parent
959
- ) {
960
- featureFromLayout = f.annotationFeature
961
- }
962
- }
963
- }
964
-
965
- if (!featureFromLayout) {
966
- featureFromLayout = featuresForRow.at(-1)?.annotationFeature
967
- }
968
-
969
- return featureFromLayout
970
- }
971
-
972
- getRowForFeature(
973
- feature: AnnotationFeatureI,
974
- childFeature: AnnotationFeatureI,
975
- ) {
976
- const rows = this.featuresForRow(feature)
977
- for (const [idx, row] of rows.entries()) {
978
- if (
979
- row.some(
980
- (feature) => feature.annotationFeature._id === childFeature._id,
981
- )
982
- ) {
983
- return idx
984
- }
985
- }
986
- return
987
- }
988
-
989
- async executeDrag(stateModel: LinearApolloDisplay) {
990
- const {
991
- apolloDragging,
992
- changeManager,
993
- displayedRegions,
994
- getAssemblyId,
995
- setCursor,
996
- } = stateModel
997
- if (!apolloDragging) {
998
- return
999
- }
1000
- const {
1001
- discontinuousLocation,
1002
- feature,
1003
- glyph,
1004
- mousePosition: startingMousePosition,
1005
- topLevelFeature,
1006
- } = apolloDragging.start
1007
- if (!feature) {
1008
- throw new Error('no feature for drag preview??')
1009
- }
1010
- if (glyph !== this) {
1011
- throw new Error('drawDragPreview() called on wrong glyph?')
1012
- }
1013
- const edge = this.isMouseOnFeatureEdge(
1014
- startingMousePosition,
1015
- feature,
1016
- stateModel,
1017
- )
1018
- if (!edge) {
1019
- return
1020
- }
1021
-
1022
- const { mousePosition: currentMousePosition } = apolloDragging.current
1023
- const region = displayedRegions[startingMousePosition.regionNumber]
1024
- const newBp = currentMousePosition.bp
1025
- const assembly = getAssemblyId(region.assemblyName)
1026
- const changes: (
1027
- | LocationStartChange
1028
- | LocationEndChange
1029
- | DiscontinuousLocationEndChange
1030
- | DiscontinuousLocationStartChange
1031
- )[] = []
1032
-
1033
- if (edge === 'start') {
1034
- if (
1035
- discontinuousLocation?.idx !== undefined &&
1036
- feature.discontinuousLocations &&
1037
- feature.discontinuousLocations.length > 0
1038
- ) {
1039
- const oldStart =
1040
- feature.discontinuousLocations[discontinuousLocation.idx].start
1041
- this.addDiscontinuousLocStartChange(
1042
- changes,
1043
- feature,
1044
- newBp,
1045
- oldStart,
1046
- assembly,
1047
- discontinuousLocation.idx,
1048
- )
1049
-
1050
- const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
1051
- const exonForCds = this.adjacentExonsOfCdsDL(
1052
- discontinuousLocation,
1053
- exonCDSRelations,
1054
- )
1055
- if (
1056
- exonForCds &&
1057
- exonForCds.matchingExon &&
1058
- newBp < exonForCds.matchingExon.start
1059
- ) {
1060
- this.addStartLocationChange(
1061
- changes,
1062
- exonForCds.matchingExon,
1063
- newBp,
1064
- assembly,
1065
- )
1066
- }
1067
- } else {
1068
- this.addStartLocationChange(changes, feature, newBp, assembly)
1069
- }
1070
- } else {
1071
- if (
1072
- discontinuousLocation?.idx !== undefined &&
1073
- feature.discontinuousLocations &&
1074
- feature.discontinuousLocations.length > 0
1075
- ) {
1076
- const oldEnd =
1077
- feature.discontinuousLocations[discontinuousLocation.idx].end
1078
- this.addDiscontinuousLocEndChange(
1079
- changes,
1080
- feature,
1081
- newBp,
1082
- oldEnd,
1083
- assembly,
1084
- discontinuousLocation.idx,
1085
- )
1086
-
1087
- const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature)
1088
- const exonForCds = this.adjacentExonsOfCdsDL(
1089
- discontinuousLocation,
1090
- exonCDSRelations,
1091
- )
1092
- if (
1093
- exonForCds &&
1094
- exonForCds.matchingExon &&
1095
- newBp > exonForCds.matchingExon.end
1096
- ) {
1097
- this.addEndLocationChange(
1098
- changes,
1099
- exonForCds.matchingExon,
1100
- newBp,
1101
- assembly,
1102
- )
1103
- }
1104
- } else {
1105
- this.addEndLocationChange(changes, feature, newBp, assembly)
1106
- }
1107
- }
1108
-
1109
- if (!changeManager) {
1110
- throw new Error('no change manager')
1111
- }
1112
- for (const change of changes) {
1113
- await changeManager.submit(change)
1114
- }
1115
-
1116
- setCursor()
1117
- }
1118
-
1119
- addDiscontinuousLocStartChange(
1120
- changes: LocationChange[],
1121
- feature: AnnotationFeatureI, // cds
1122
- newBp: number,
1123
- oldStart: number,
1124
- assembly: string,
1125
- index: number,
1126
- ) {
1127
- const featureId = feature._id
1128
- changes.push(
1129
- new DiscontinuousLocationStartChange({
1130
- typeName: 'DiscontinuousLocationStartChange',
1131
- changedIds: [feature._id],
1132
- featureId,
1133
- newStart: newBp,
1134
- oldStart,
1135
- index,
1136
- assembly,
1137
- }),
1138
- )
1139
- }
1140
-
1141
- addDiscontinuousLocEndChange(
1142
- changes: LocationChange[],
1143
- feature: AnnotationFeatureI, // cds
1144
- newBp: number,
1145
- oldEnd: number,
1146
- assembly: string,
1147
- index: number,
1148
- ) {
1149
- const featureId = feature._id
1150
- changes.push(
1151
- new DiscontinuousLocationEndChange({
1152
- typeName: 'DiscontinuousLocationEndChange',
1153
- changedIds: [feature._id],
1154
- featureId,
1155
- newEnd: newBp,
1156
- oldEnd,
1157
- index,
1158
- assembly,
1159
- }),
1160
- )
1161
- }
1162
-
1163
- addEndLocationChange(
1164
- changes: LocationChange[],
1165
- feature: AnnotationFeatureI,
1166
- newBp: number,
1167
- assembly: string,
1168
- ) {
1169
- const featureId = feature._id
1170
- const oldEnd = feature.end
1171
- const newEnd = newBp
1172
- changes.push(
1173
- new LocationEndChange({
1174
- typeName: 'LocationEndChange',
1175
- changedIds: [featureId],
1176
- featureId,
1177
- oldEnd,
1178
- newEnd,
1179
- assembly,
1180
- }),
1181
- )
1182
- }
1183
-
1184
- addStartLocationChange(
1185
- changes: LocationChange[],
1186
- feature: AnnotationFeatureI,
1187
- newBp: number,
1188
- assembly: string,
1189
- ) {
1190
- const featureId = feature._id
1191
- const oldStart = feature.start
1192
- const newStart = newBp
1193
- changes.push(
1194
- new LocationStartChange({
1195
- typeName: 'LocationStartChange',
1196
- changedIds: [featureId],
1197
- featureId,
1198
- oldStart,
1199
- newStart,
1200
- assembly,
1201
- }),
1202
- )
1203
- }
1204
- }