@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.8

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 (71) hide show
  1. package/dist/index.esm.js +2371 -1642
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
  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 +4387 -2952
  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 +15 -15
  12. package/src/ApolloInternetAccount/model.ts +48 -13
  13. package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
  14. package/src/ChangeManager.ts +33 -13
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  17. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
  21. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  22. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  23. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  24. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  25. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  26. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  27. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  28. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  29. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  34. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +95 -38
  35. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
  36. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  37. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  38. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  39. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  40. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  41. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  42. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  43. package/src/components/AddAssembly.tsx +1 -1
  44. package/src/components/AddAssemblyAliases.tsx +1 -1
  45. package/src/components/AddChildFeature.tsx +5 -2
  46. package/src/components/AddFeature.tsx +9 -3
  47. package/src/components/AddRefSeqAliases.tsx +9 -9
  48. package/src/components/CopyFeature.tsx +3 -1
  49. package/src/components/CreateApolloAnnotation.tsx +1 -0
  50. package/src/components/DeleteAssembly.tsx +1 -1
  51. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  52. package/src/components/FilterFeatures.tsx +7 -7
  53. package/src/components/FilterTranscripts.tsx +6 -6
  54. package/src/components/ImportFeatures.tsx +1 -1
  55. package/src/components/ManageChecks.tsx +1 -1
  56. package/src/components/MergeTranscripts.tsx +12 -15
  57. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  58. package/src/components/OpenLocalFile.tsx +11 -7
  59. package/src/components/ViewCheckResults.tsx +1 -1
  60. package/src/components/index.ts +1 -0
  61. package/src/config.ts +6 -0
  62. package/src/index.ts +42 -105
  63. package/src/makeDisplayComponent.tsx +0 -1
  64. package/src/menus/index.ts +1 -0
  65. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  66. package/src/menus/topLevelMenuAdmin.ts +154 -0
  67. package/src/session/session.ts +162 -116
  68. package/src/util/annotationFeatureUtils.ts +15 -21
  69. package/src/util/displayUtils.ts +149 -0
  70. package/src/util/glyphUtils.ts +152 -0
  71. package/src/util/mouseEventsUtils.ts +32 -0
@@ -0,0 +1,481 @@
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
+ import type PluginManager from '@jbrowse/core/PluginManager'
3
+ import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
4
+ import {
5
+ type Frame,
6
+ defaultCodonTable,
7
+ getFrame,
8
+ revcom,
9
+ } from '@jbrowse/core/util'
10
+ import { type Theme, createTheme } from '@mui/material'
11
+ import { autorun } from 'mobx'
12
+ import { type Instance, addDisposer } from 'mobx-state-tree'
13
+
14
+ import { type ApolloSessionModel } from '../../session'
15
+
16
+ import { baseModelFactory } from './base'
17
+
18
+ function colorCode(letter: string, theme: Theme) {
19
+ return (
20
+ theme.palette.bases[
21
+ letter.toUpperCase() as keyof Theme['palette']['bases']
22
+ ].main.toString() ?? 'lightgray'
23
+ )
24
+ }
25
+
26
+ function codonColorCode(letter: string, highContrast?: boolean) {
27
+ const colorMap: Record<string, string | undefined> = {
28
+ M: '#33ee33',
29
+ '*': highContrast ? '#000000' : '#f44336',
30
+ }
31
+
32
+ return colorMap[letter.toUpperCase()]
33
+ }
34
+
35
+ function reverseCodonSeq(seq: string): string {
36
+ // disable because sequence is all ascii
37
+ // eslint-disable-next-line @typescript-eslint/no-misused-spread
38
+ return [...seq]
39
+ .map((c) => revcom(c))
40
+ .reverse()
41
+ .join('')
42
+ }
43
+
44
+ function drawLetter(
45
+ seqTrackctx: CanvasRenderingContext2D,
46
+ startPx: number,
47
+ widthPx: number,
48
+ letter: string,
49
+ textY: number,
50
+ ) {
51
+ const fontSize = Math.min(widthPx, 10)
52
+ seqTrackctx.fillStyle = '#000'
53
+ seqTrackctx.font = `${fontSize}px`
54
+ const textWidth = seqTrackctx.measureText(letter).width
55
+ const textX = startPx + (widthPx - textWidth) / 2
56
+ seqTrackctx.fillText(letter, textX, textY + 10)
57
+ }
58
+
59
+ function drawTranslation(
60
+ seqTrackctx: CanvasRenderingContext2D,
61
+ bpPerPx: number,
62
+ trnslStartPx: number,
63
+ trnslY: number,
64
+ trnslWidthPx: number,
65
+ sequenceRowHeight: number,
66
+ seq: string,
67
+ i: number,
68
+ reverse: boolean,
69
+ showStartCodons: boolean,
70
+ showStopCodons: boolean,
71
+ highContrast: boolean,
72
+ ) {
73
+ let codonSeq: string = seq.slice(i, i + 3).toUpperCase()
74
+ if (reverse) {
75
+ codonSeq = reverseCodonSeq(codonSeq)
76
+ }
77
+ const codonLetter =
78
+ defaultCodonTable[codonSeq as keyof typeof defaultCodonTable]
79
+ if (!codonLetter) {
80
+ return
81
+ }
82
+ const fillColor = codonColorCode(codonLetter, highContrast)
83
+ if (
84
+ fillColor &&
85
+ ((showStopCodons && codonLetter == '*') ||
86
+ (showStartCodons && codonLetter != '*'))
87
+ ) {
88
+ seqTrackctx.fillStyle = fillColor
89
+ seqTrackctx.fillRect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight)
90
+ }
91
+ if (bpPerPx <= 0.1) {
92
+ seqTrackctx.rect(trnslStartPx, trnslY, trnslWidthPx, sequenceRowHeight)
93
+ seqTrackctx.stroke()
94
+ drawLetter(seqTrackctx, trnslStartPx, trnslWidthPx, codonLetter, trnslY)
95
+ }
96
+ }
97
+
98
+ function getTranslationRow(frame: Frame, bpPerPx: number) {
99
+ const offset = bpPerPx <= 1 ? 2 : 0
100
+ switch (frame) {
101
+ case 3: {
102
+ return 0
103
+ }
104
+ case 2: {
105
+ return 1
106
+ }
107
+ case 1: {
108
+ return 2
109
+ }
110
+ case -1: {
111
+ return 3 + offset
112
+ }
113
+ case -2: {
114
+ return 4 + offset
115
+ }
116
+ case -3: {
117
+ return 5 + offset
118
+ }
119
+ }
120
+ }
121
+
122
+ function getSeqRow(
123
+ strand: 1 | -1 | undefined,
124
+ bpPerPx: number,
125
+ ): number | undefined {
126
+ if (bpPerPx > 1 || strand === undefined) {
127
+ return
128
+ }
129
+ return strand === 1 ? 3 : 4
130
+ }
131
+
132
+ function highlightSeq(
133
+ seqTrackOverlayctx: CanvasRenderingContext2D,
134
+ theme: Theme,
135
+ startPx: number,
136
+ sequenceRowHeight: number,
137
+ row: number | undefined,
138
+ widthPx: number,
139
+ ) {
140
+ if (row !== undefined) {
141
+ seqTrackOverlayctx.fillStyle = theme.palette.action.focus
142
+ seqTrackOverlayctx.fillRect(
143
+ startPx,
144
+ sequenceRowHeight * row,
145
+ widthPx,
146
+ sequenceRowHeight,
147
+ )
148
+ }
149
+ }
150
+
151
+ export function renderingModelFactory(
152
+ pluginManager: PluginManager,
153
+ configSchema: AnyConfigurationSchemaType,
154
+ ) {
155
+ const BaseLinearApolloReferenceSequenceDisplay = baseModelFactory(
156
+ pluginManager,
157
+ configSchema,
158
+ )
159
+
160
+ return BaseLinearApolloReferenceSequenceDisplay.named(
161
+ 'LinearApolloReferenceSequenceDisplayRendering',
162
+ )
163
+ .volatile(() => ({
164
+ seqTrackCanvas: null as HTMLCanvasElement | null,
165
+ seqTrackOverlayCanvas: null as HTMLCanvasElement | null,
166
+ theme: createTheme(),
167
+ }))
168
+ .actions((self) => ({
169
+ setSeqTrackCanvas(canvas: HTMLCanvasElement | null) {
170
+ self.seqTrackCanvas = canvas
171
+ },
172
+ setSeqTrackOverlayCanvas(canvas: HTMLCanvasElement | null) {
173
+ self.seqTrackOverlayCanvas = canvas
174
+ },
175
+ setTheme(theme: Theme) {
176
+ self.theme = theme
177
+ },
178
+ afterAttach() {
179
+ addDisposer(
180
+ self,
181
+ autorun(
182
+ () => {
183
+ const { theme } = self
184
+ if (!self.lgv.initialized || self.regionCannotBeRendered()) {
185
+ return
186
+ }
187
+ const trnslWidthPx = 3 / self.lgv.bpPerPx
188
+ if (trnslWidthPx < 1) {
189
+ return
190
+ }
191
+ const seqTrackctx = self.seqTrackCanvas?.getContext('2d')
192
+ if (!seqTrackctx) {
193
+ return
194
+ }
195
+
196
+ seqTrackctx.clearRect(
197
+ 0,
198
+ 0,
199
+ self.lgv.dynamicBlocks.totalWidthPx,
200
+ self.height,
201
+ )
202
+ const frames =
203
+ self.lgv.bpPerPx <= 1
204
+ ? [3, 2, 1, 0, 0, -1, -2, -3]
205
+ : [3, 2, 1, -1, -2, -3]
206
+ let height = 0
207
+ if (theme) {
208
+ for (const frame of frames) {
209
+ let frameColor = theme.palette.framesCDS.at(frame)?.main
210
+ if (frameColor) {
211
+ let offsetPx = 0
212
+ if (self.highContrast) {
213
+ frameColor = 'white'
214
+ offsetPx = 1
215
+ // eslint-disable-next-line prefer-destructuring
216
+ seqTrackctx.fillStyle = theme.palette.grey[200]
217
+ seqTrackctx.fillRect(
218
+ 0,
219
+ height,
220
+ self.lgv.dynamicBlocks.totalWidthPx,
221
+ self.sequenceRowHeight,
222
+ )
223
+ }
224
+ seqTrackctx.fillStyle = frameColor
225
+ seqTrackctx.fillRect(
226
+ 0 + offsetPx,
227
+ height + offsetPx,
228
+ self.lgv.dynamicBlocks.totalWidthPx - 2 * offsetPx,
229
+ self.sequenceRowHeight - 2 * offsetPx,
230
+ )
231
+ }
232
+ height += self.sequenceRowHeight
233
+ }
234
+ }
235
+
236
+ for (const [idx, region] of self.regions.entries()) {
237
+ const { apolloDataStore } =
238
+ self.session as unknown as ApolloSessionModel
239
+ const assembly = apolloDataStore.assemblies.get(
240
+ region.assemblyName,
241
+ )
242
+ const ref = assembly?.getByRefName(region.refName)
243
+ const seq = ref?.getSequence(region.start, region.end)
244
+ if (!seq) {
245
+ return
246
+ }
247
+ // disable because sequence is all ascii
248
+ // eslint-disable-next-line @typescript-eslint/no-misused-spread
249
+ for (const [i, letter] of [...seq].entries()) {
250
+ const trnslXOffset =
251
+ (self.lgv.bpToPx({
252
+ refName: region.refName,
253
+ coord: region.start + i,
254
+ regionNumber: idx,
255
+ })?.offsetPx ?? 0) - self.lgv.offsetPx
256
+ const trnslStartPx = self.lgv.displayedRegions[idx].reversed
257
+ ? trnslXOffset - trnslWidthPx
258
+ : trnslXOffset
259
+
260
+ // Draw translation forward
261
+ for (let j = 2; j >= 0; j--) {
262
+ if ((region.start + i) % 3 === j) {
263
+ drawTranslation(
264
+ seqTrackctx,
265
+ self.lgv.bpPerPx,
266
+ trnslStartPx,
267
+ self.sequenceRowHeight * (2 - j),
268
+ trnslWidthPx,
269
+ self.sequenceRowHeight,
270
+ seq,
271
+ i,
272
+ false,
273
+ self.showStartCodons,
274
+ self.showStopCodons,
275
+ self.highContrast,
276
+ )
277
+ }
278
+ }
279
+
280
+ if (self.lgv.bpPerPx <= 1) {
281
+ const xOffset =
282
+ (self.lgv.bpToPx({
283
+ refName: region.refName,
284
+ coord: region.start + i,
285
+ regionNumber: idx,
286
+ })?.offsetPx ?? 0) - self.lgv.offsetPx
287
+ const widthPx = 1 / self.lgv.bpPerPx
288
+ const startPx = self.lgv.displayedRegions[idx].reversed
289
+ ? xOffset - widthPx
290
+ : xOffset
291
+
292
+ // Draw forward
293
+ seqTrackctx.beginPath()
294
+ seqTrackctx.fillStyle = colorCode(letter, self.theme)
295
+ seqTrackctx.rect(
296
+ startPx,
297
+ self.sequenceRowHeight * 3,
298
+ widthPx,
299
+ self.sequenceRowHeight,
300
+ )
301
+ seqTrackctx.fill()
302
+ if (self.lgv.bpPerPx <= 0.1) {
303
+ seqTrackctx.stroke()
304
+ drawLetter(
305
+ seqTrackctx,
306
+ startPx,
307
+ widthPx,
308
+ letter,
309
+ self.sequenceRowHeight * 3,
310
+ )
311
+ }
312
+
313
+ // Draw reverse
314
+ const revLetter = revcom(letter)
315
+ seqTrackctx.beginPath()
316
+ seqTrackctx.fillStyle = colorCode(revLetter, self.theme)
317
+ seqTrackctx.rect(
318
+ startPx,
319
+ self.sequenceRowHeight * 4,
320
+ widthPx,
321
+ self.sequenceRowHeight,
322
+ )
323
+ seqTrackctx.fill()
324
+ if (self.lgv.bpPerPx <= 0.1) {
325
+ seqTrackctx.stroke()
326
+ drawLetter(
327
+ seqTrackctx,
328
+ startPx,
329
+ widthPx,
330
+ revLetter,
331
+ self.sequenceRowHeight * 4,
332
+ )
333
+ }
334
+ }
335
+
336
+ // Draw translation reverse
337
+ for (let k = 0; k <= 2; k++) {
338
+ const rowOffset = self.lgv.bpPerPx <= 1 ? 5 : 3
339
+ if ((region.start + i) % 3 === k) {
340
+ drawTranslation(
341
+ seqTrackctx,
342
+ self.lgv.bpPerPx,
343
+ trnslStartPx,
344
+ self.sequenceRowHeight * (rowOffset + k),
345
+ trnslWidthPx,
346
+ self.sequenceRowHeight,
347
+ seq,
348
+ i,
349
+ true,
350
+ self.showStartCodons,
351
+ self.showStopCodons,
352
+ self.highContrast,
353
+ )
354
+ }
355
+ }
356
+ }
357
+ }
358
+ },
359
+ { name: 'LinearApolloReferenceSequenceDisplayRenderSequence' },
360
+ ),
361
+ )
362
+ addDisposer(
363
+ self,
364
+ autorun(
365
+ () => {
366
+ if (!self.lgv.initialized || self.regionCannotBeRendered()) {
367
+ return
368
+ }
369
+ const seqTrackOverlayctx =
370
+ self.seqTrackOverlayCanvas?.getContext('2d')
371
+ if (!seqTrackOverlayctx) {
372
+ return
373
+ }
374
+
375
+ seqTrackOverlayctx.clearRect(
376
+ 0,
377
+ 0,
378
+ self.lgv.dynamicBlocks.totalWidthPx,
379
+ self.height,
380
+ )
381
+
382
+ const {
383
+ hoveredFeature,
384
+ lgv,
385
+ regions,
386
+ sequenceRowHeight,
387
+ session,
388
+ theme,
389
+ } = self
390
+
391
+ if (!hoveredFeature) {
392
+ return
393
+ }
394
+ const { feature } = hoveredFeature
395
+
396
+ const { featureTypeOntology } =
397
+ session.apolloDataStore.ontologyManager
398
+ if (!featureTypeOntology) {
399
+ throw new Error('featureTypeOntology is undefined')
400
+ }
401
+ for (const [idx, region] of regions.entries()) {
402
+ if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
403
+ const parentFeature = feature.parent
404
+ if (!parentFeature) {
405
+ continue
406
+ }
407
+ const cdsLocs = parentFeature.cdsLocations.find(
408
+ (loc) =>
409
+ feature.min === loc.at(0)?.min &&
410
+ feature.max === loc.at(-1)?.max,
411
+ )
412
+ if (!cdsLocs) {
413
+ continue
414
+ }
415
+ for (const dl of cdsLocs) {
416
+ const frame = getFrame(
417
+ dl.min,
418
+ dl.max,
419
+ feature.strand ?? 1,
420
+ dl.phase,
421
+ )
422
+ const row = getTranslationRow(frame, lgv.bpPerPx)
423
+ const offset =
424
+ (lgv.bpToPx({
425
+ refName: region.refName,
426
+ coord: dl.min,
427
+ regionNumber: idx,
428
+ })?.offsetPx ?? 0) - lgv.offsetPx
429
+ const widthPx = (dl.max - dl.min) / lgv.bpPerPx
430
+ const startPx = lgv.displayedRegions[idx].reversed
431
+ ? offset - widthPx
432
+ : offset
433
+
434
+ highlightSeq(
435
+ seqTrackOverlayctx,
436
+ theme,
437
+ startPx,
438
+ sequenceRowHeight,
439
+ row,
440
+ widthPx,
441
+ )
442
+ }
443
+ } else {
444
+ const row = getSeqRow(feature.strand, lgv.bpPerPx)
445
+ const offset =
446
+ (lgv.bpToPx({
447
+ refName: region.refName,
448
+ coord: feature.min,
449
+ regionNumber: idx,
450
+ })?.offsetPx ?? 0) - lgv.offsetPx
451
+ const widthPx = feature.length / lgv.bpPerPx
452
+ const startPx = lgv.displayedRegions[idx].reversed
453
+ ? offset - widthPx
454
+ : offset
455
+
456
+ highlightSeq(
457
+ seqTrackOverlayctx,
458
+ theme,
459
+ startPx,
460
+ sequenceRowHeight,
461
+ row,
462
+ widthPx,
463
+ )
464
+ }
465
+ }
466
+ },
467
+ { name: 'LinearApolloDisplayRenderSeqHighlight' },
468
+ ),
469
+ )
470
+ },
471
+ }))
472
+ }
473
+
474
+ export type LinearApolloReferenceSequenceDisplayRenderingModel = ReturnType<
475
+ typeof renderingModelFactory
476
+ >
477
+ // eslint disable because of
478
+ // https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
479
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
480
+ export interface LinearApolloReferenceSequenceDisplayRendering
481
+ extends Instance<LinearApolloReferenceSequenceDisplayRenderingModel> {}
@@ -1,19 +1,25 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
+ import { type CheckResultI } from '@apollo-annotation/mst'
4
5
  import { Menu, type MenuItem } from '@jbrowse/core/ui'
5
6
  import {
6
7
  type AbstractSessionModel,
7
8
  doesIntersect2,
8
9
  getContainingView,
10
+ getFrame,
9
11
  } from '@jbrowse/core/util'
10
12
  import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
11
13
  import ErrorIcon from '@mui/icons-material/Error'
12
- import { Alert, Avatar, Tooltip, useTheme } from '@mui/material'
14
+ import { Alert, Avatar, Badge, Box, Tooltip, useTheme } from '@mui/material'
13
15
  import { observer } from 'mobx-react'
14
16
  import React, { useEffect, useState } from 'react'
15
- import { makeStyles } from 'tss-react/mui'
16
17
 
18
+ import {
19
+ type Coord,
20
+ clusterResultByMessage,
21
+ useStyles,
22
+ } from '../../util/displayUtils'
17
23
  import { type LinearApolloSixFrameDisplay as LinearApolloSixFrameDisplayI } from '../stateModel'
18
24
 
19
25
  import { TrackLines } from './TrackLines'
@@ -21,31 +27,11 @@ import { TrackLines } from './TrackLines'
21
27
  interface LinearApolloSixFrameDisplayProps {
22
28
  model: LinearApolloSixFrameDisplayI
23
29
  }
24
- export type Coord = [number, number]
25
-
26
- const useStyles = makeStyles()((theme) => ({
27
- canvasContainer: {
28
- position: 'relative',
29
- left: 0,
30
- },
31
- canvas: {
32
- position: 'absolute',
33
- left: 0,
34
- },
35
- ellipses: {
36
- textOverflow: 'ellipsis',
37
- overflow: 'hidden',
38
- },
39
- avatar: {
40
- position: 'absolute',
41
- color: theme.palette.warning.light,
42
- backgroundColor: theme.palette.warning.contrastText,
43
- },
44
- }))
45
30
 
46
31
  export const LinearApolloSixFrameDisplay = observer(
47
32
  function LinearApolloSixFrameDisplay(
48
33
  props: LinearApolloSixFrameDisplayProps,
34
+ apolloDragging,
49
35
  ) {
50
36
  const theme = useTheme()
51
37
  const { model } = props
@@ -54,6 +40,8 @@ export const LinearApolloSixFrameDisplay = observer(
54
40
  contextMenuItems: getContextMenuItems,
55
41
  cursor,
56
42
  featuresHeight,
43
+ featureLabelSpacer,
44
+ geneTrackRowNums,
57
45
  isShown,
58
46
  onMouseDown,
59
47
  onMouseLeave,
@@ -65,6 +53,7 @@ export const LinearApolloSixFrameDisplay = observer(
65
53
  setCollaboratorCanvas,
66
54
  setOverlayCanvas,
67
55
  setTheme,
56
+ showCheckResults,
68
57
  } = model
69
58
  const { classes } = useStyles()
70
59
  const lgv = getContainingView(model) as unknown as LinearGenomeViewModel
@@ -95,12 +84,16 @@ export const LinearApolloSixFrameDisplay = observer(
95
84
  } else {
96
85
  const coord: [number, number] = [event.clientX, event.clientY]
97
86
  setContextCoord(coord)
98
- setContextMenuItems(getContextMenuItems(coord))
87
+ setContextMenuItems(getContextMenuItems(event))
99
88
  }
100
89
  }}
101
90
  >
102
91
  {message ? (
103
- <Alert severity="warning" classes={{ message: classes.ellipses }}>
92
+ <Alert
93
+ severity="warning"
94
+ classes={{ message: classes.ellipses }}
95
+ slotProps={{ root: { className: classes.center } }}
96
+ >
104
97
  <Tooltip title={message}>
105
98
  <div>{message}</div>
106
99
  </Tooltip>
@@ -152,9 +145,12 @@ export const LinearApolloSixFrameDisplay = observer(
152
145
  data-testid="overlayCanvas"
153
146
  />
154
147
  {lgv.displayedRegions.flatMap((region, idx) => {
148
+ const widthBp = lgv.bpPerPx * apolloRowHeight
155
149
  const assembly = assemblyManager.get(region.assemblyName)
156
- return [...session.apolloDataStore.checkResults.values()]
157
- .filter(
150
+ if (showCheckResults) {
151
+ const filteredCheckResults = [
152
+ ...session.apolloDataStore.checkResults.values(),
153
+ ].filter(
158
154
  (checkResult) =>
159
155
  assembly?.isValidRefName(checkResult.refSeq) &&
160
156
  assembly.getCanonicalRefName(checkResult.refSeq) ===
@@ -166,33 +162,92 @@ export const LinearApolloSixFrameDisplay = observer(
166
162
  checkResult.end,
167
163
  ),
168
164
  )
169
- .map((checkResult) => {
165
+ const checkResults = clusterResultByMessage<CheckResultI>(
166
+ filteredCheckResults,
167
+ widthBp,
168
+ true,
169
+ )
170
+ return checkResults.map((checkResult) => {
170
171
  const left =
171
172
  (lgv.bpToPx({
172
173
  refName: region.refName,
173
174
  coord: checkResult.start,
174
175
  regionNumber: idx,
175
176
  })?.offsetPx ?? 0) - lgv.offsetPx
176
- const [feature] = checkResult.ids
177
+ const [feature] = checkResult.featureIds
177
178
  if (!feature || !feature.parent?.looksLikeGene) {
178
179
  return null
179
180
  }
180
- const top = 0
181
+
182
+ let row
183
+ for (const loc of feature.cdsLocations) {
184
+ for (const cds of loc) {
185
+ let rowNum: number = getFrame(
186
+ cds.min,
187
+ cds.max,
188
+ feature.strand ?? 1,
189
+ cds.phase,
190
+ )
191
+ rowNum = featureLabelSpacer(
192
+ rowNum < 0 ? -1 * rowNum + 5 : rowNum,
193
+ )
194
+ if (
195
+ checkResult.start >= cds.min &&
196
+ checkResult.start <= cds.max
197
+ ) {
198
+ row = rowNum - 1
199
+ break
200
+ }
201
+ }
202
+ }
203
+ if (row === undefined) {
204
+ const rowNum =
205
+ feature.strand == 1
206
+ ? geneTrackRowNums[0]
207
+ : geneTrackRowNums[1]
208
+ row = rowNum - 1
209
+ }
210
+
211
+ const top = row * apolloRowHeight
181
212
  const height = apolloRowHeight
182
213
  return (
183
214
  <Tooltip
184
215
  key={checkResult._id}
185
216
  title={checkResult.message}
186
217
  >
187
- <Avatar
188
- className={classes.avatar}
189
- style={{ top, left, height, width: height }}
218
+ <Box
219
+ className={classes.box}
220
+ style={{
221
+ top,
222
+ left,
223
+ height,
224
+ width: height,
225
+ pointerEvents: apolloDragging ? 'none' : 'auto',
226
+ }}
190
227
  >
191
- <ErrorIcon />
192
- </Avatar>
228
+ <Badge
229
+ className={classes.badge}
230
+ badgeContent={checkResult.count}
231
+ color="primary"
232
+ overlap="circular"
233
+ anchorOrigin={{
234
+ vertical: 'bottom',
235
+ horizontal: 'right',
236
+ }}
237
+ invisible={checkResult.count <= 1}
238
+ >
239
+ <Avatar className={classes.avatar}>
240
+ <ErrorIcon
241
+ data-testid={`ErrorIcon-${checkResult.start}`}
242
+ />
243
+ </Avatar>
244
+ </Badge>
245
+ </Box>
193
246
  </Tooltip>
194
247
  )
195
248
  })
249
+ }
250
+ return null
196
251
  })}
197
252
  <Menu
198
253
  open={contextMenuItems.length > 0}
@@ -203,9 +258,11 @@ export const LinearApolloSixFrameDisplay = observer(
203
258
  onClose={() => {
204
259
  setContextMenuItems([])
205
260
  }}
206
- TransitionProps={{
207
- onExit: () => {
208
- setContextMenuItems([])
261
+ slotProps={{
262
+ transition: {
263
+ onExit: () => {
264
+ setContextMenuItems([])
265
+ },
209
266
  },
210
267
  }}
211
268
  anchorReference="anchorPosition"