@apollo-annotation/jbrowse-plugin-apollo 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +10932 -10932
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10845 -10846
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +18619 -21342
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +7 -7
- package/src/ApolloInternetAccount/model.ts +81 -63
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +49 -18
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +3 -1
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +39 -203
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -102
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +31 -230
- package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +62 -386
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +6 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +122 -70
- package/src/components/AddAssembly.tsx +33 -37
- package/src/components/AddFeature.tsx +21 -18
- package/src/components/AddRefSeqAliases.tsx +56 -42
- package/src/components/CopyFeature.tsx +1 -1
- package/src/components/CreateApolloAnnotation.tsx +22 -10
- package/src/components/DeleteAssembly.tsx +2 -9
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/ManageChecks.tsx +2 -9
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/OntologyTermAutocomplete.tsx +1 -8
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +1 -7
- package/src/config.ts +3 -3
- package/src/index.ts +17 -16
- package/src/makeDisplayComponent.tsx +9 -13
- package/src/session/ClientDataStore.ts +32 -14
- package/src/session/session.ts +19 -27
- package/src/util/glyphUtils.ts +178 -1
- package/src/util/loadAssemblyIntoClient.ts +3 -2
|
@@ -10,21 +10,24 @@ import {
|
|
|
10
10
|
isSessionModelWithWidgets,
|
|
11
11
|
} from '@jbrowse/core/util'
|
|
12
12
|
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
13
|
-
import SkipNextRoundedIcon from '@mui/icons-material/SkipNextRounded'
|
|
14
|
-
import SkipPreviousRoundedIcon from '@mui/icons-material/SkipPreviousRounded'
|
|
15
13
|
import { alpha } from '@mui/material'
|
|
16
14
|
|
|
17
15
|
import { type OntologyRecord } from '../../OntologyManager'
|
|
18
16
|
import { MergeExons, MergeTranscripts, SplitExon } from '../../components'
|
|
19
|
-
import { type ApolloSessionModel } from '../../session'
|
|
20
17
|
import {
|
|
21
18
|
type MousePosition,
|
|
22
19
|
type MousePositionWithFeature,
|
|
23
20
|
containsSelectedFeature,
|
|
21
|
+
getAdjacentExons,
|
|
24
22
|
getMinAndMaxPx,
|
|
25
23
|
getOverlappingEdge,
|
|
24
|
+
getStreamIcon,
|
|
25
|
+
isCDSFeature,
|
|
26
|
+
isExonFeature,
|
|
26
27
|
isMousePositionWithFeature,
|
|
28
|
+
isTranscriptFeature,
|
|
27
29
|
navToFeatureCenter,
|
|
30
|
+
selectFeatureAndOpenWidget,
|
|
28
31
|
} from '../../util'
|
|
29
32
|
import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
|
|
30
33
|
import { type LinearApolloDisplay } from '../stateModel'
|
|
@@ -92,9 +95,9 @@ function drawBackground(
|
|
|
92
95
|
stateModel: LinearApolloDisplayRendering,
|
|
93
96
|
displayedRegionIndex: number,
|
|
94
97
|
row: number,
|
|
95
|
-
color
|
|
98
|
+
color?: string,
|
|
96
99
|
) {
|
|
97
|
-
const { apolloRowHeight, lgv, session } = stateModel
|
|
100
|
+
const { apolloRowHeight, lgv, session, theme } = stateModel
|
|
98
101
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
99
102
|
const displayedRegion = displayedRegions[displayedRegionIndex]
|
|
100
103
|
const { refName, reversed } = displayedRegion
|
|
@@ -118,7 +121,20 @@ function drawBackground(
|
|
|
118
121
|
const topLevelFeatureHeight =
|
|
119
122
|
getRowCount(feature, featureTypeOntology) * apolloRowHeight
|
|
120
123
|
|
|
121
|
-
|
|
124
|
+
let selectedColor
|
|
125
|
+
if (color) {
|
|
126
|
+
selectedColor = color
|
|
127
|
+
} else {
|
|
128
|
+
selectedColor = readConfObject(
|
|
129
|
+
session.getPluginConfiguration(),
|
|
130
|
+
'geneBackgroundColor',
|
|
131
|
+
{ featureType: feature.type },
|
|
132
|
+
) as string
|
|
133
|
+
if (!selectedColor) {
|
|
134
|
+
selectedColor = alpha(theme.palette.background.paper, 0.6)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
ctx.fillStyle = selectedColor
|
|
122
138
|
ctx.fillRect(
|
|
123
139
|
topLevelFeatureStartPx,
|
|
124
140
|
topLevelFeatureTop,
|
|
@@ -127,18 +143,6 @@ function drawBackground(
|
|
|
127
143
|
)
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
function backgroundColorForFeature(
|
|
131
|
-
session: ApolloSessionModel,
|
|
132
|
-
featureType: string,
|
|
133
|
-
): string {
|
|
134
|
-
const color = readConfObject(
|
|
135
|
-
session.getPluginConfiguration(),
|
|
136
|
-
'backgroundColorForFeature',
|
|
137
|
-
{ featureType },
|
|
138
|
-
) as string
|
|
139
|
-
return color
|
|
140
|
-
}
|
|
141
|
-
|
|
142
146
|
function draw(
|
|
143
147
|
ctx: CanvasRenderingContext2D,
|
|
144
148
|
feature: AnnotationFeature,
|
|
@@ -163,25 +167,7 @@ function draw(
|
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
// Draw background for gene
|
|
166
|
-
drawBackground(
|
|
167
|
-
ctx,
|
|
168
|
-
feature,
|
|
169
|
-
stateModel,
|
|
170
|
-
displayedRegionIndex,
|
|
171
|
-
row,
|
|
172
|
-
alpha(theme.palette.background.paper, 0.6),
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if (featureTypeOntology.isTypeOf(feature.type, 'pseudogene')) {
|
|
176
|
-
drawBackground(
|
|
177
|
-
ctx,
|
|
178
|
-
feature,
|
|
179
|
-
stateModel,
|
|
180
|
-
displayedRegionIndex,
|
|
181
|
-
row,
|
|
182
|
-
backgroundColorForFeature(session, 'pseudogenic_transcript'),
|
|
183
|
-
)
|
|
184
|
-
}
|
|
170
|
+
drawBackground(ctx, feature, stateModel, displayedRegionIndex, row)
|
|
185
171
|
|
|
186
172
|
// Draw lines on different rows for each transcript
|
|
187
173
|
let currentRow = 0
|
|
@@ -197,30 +183,7 @@ function draw(
|
|
|
197
183
|
if (!transcriptChildren) {
|
|
198
184
|
continue
|
|
199
185
|
}
|
|
200
|
-
|
|
201
186
|
const cdsCount = getCDSCount(transcript, featureTypeOntology)
|
|
202
|
-
if (cdsCount === 0) {
|
|
203
|
-
drawBackground(
|
|
204
|
-
ctx,
|
|
205
|
-
transcript,
|
|
206
|
-
stateModel,
|
|
207
|
-
displayedRegionIndex,
|
|
208
|
-
currentRow,
|
|
209
|
-
backgroundColorForFeature(session, 'nonCodingTranscript'),
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
if (
|
|
213
|
-
featureTypeOntology.isTypeOf(transcript.type, 'pseudogenic_transcript')
|
|
214
|
-
) {
|
|
215
|
-
drawBackground(
|
|
216
|
-
ctx,
|
|
217
|
-
transcript,
|
|
218
|
-
stateModel,
|
|
219
|
-
displayedRegionIndex,
|
|
220
|
-
currentRow,
|
|
221
|
-
backgroundColorForFeature(session, 'pseudogenic_transcript'),
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
187
|
|
|
225
188
|
for (const [, childFeature] of transcriptChildren) {
|
|
226
189
|
if (!featureTypeOntology.isTypeOf(childFeature.type, 'CDS')) {
|
|
@@ -471,19 +434,21 @@ function drawLine(
|
|
|
471
434
|
ctx.strokeStyle = theme.palette.text.primary
|
|
472
435
|
const { strand = 1 } = transcript
|
|
473
436
|
ctx.beginPath()
|
|
437
|
+
// If view is reversed, draw forward as reverse and vice versa
|
|
438
|
+
const effectiveStrand = strand * (reversed ? -1 : 1)
|
|
474
439
|
// Draw the transcript line, and extend it out a bit on the 3` end
|
|
475
|
-
const lineStart = startPx - (
|
|
476
|
-
const lineEnd = startPx + widthPx + (
|
|
440
|
+
const lineStart = startPx - (effectiveStrand === -1 ? 5 : 0)
|
|
441
|
+
const lineEnd = startPx + widthPx + (effectiveStrand === -1 ? 0 : 5)
|
|
477
442
|
ctx.moveTo(lineStart, height)
|
|
478
443
|
ctx.lineTo(lineEnd, height)
|
|
479
444
|
// Now to draw arrows every 20 pixels along the line
|
|
480
445
|
// Make the arrow range a bit shorter to avoid an arrow hanging off the 5` end
|
|
481
|
-
const arrowsStart = lineStart + (
|
|
482
|
-
const arrowsEnd = lineEnd - (
|
|
446
|
+
const arrowsStart = lineStart + (effectiveStrand === -1 ? 0 : 3)
|
|
447
|
+
const arrowsEnd = lineEnd - (effectiveStrand === -1 ? 3 : 0)
|
|
483
448
|
// Offset determines if the arrows face left or right
|
|
484
|
-
const offset =
|
|
449
|
+
const offset = effectiveStrand === -1 ? 3 : -3
|
|
485
450
|
const arrowRange =
|
|
486
|
-
|
|
451
|
+
effectiveStrand === -1
|
|
487
452
|
? range(arrowsStart, arrowsEnd, 20)
|
|
488
453
|
: range(arrowsEnd, arrowsStart, 20)
|
|
489
454
|
for (const arrowLocation of arrowRange) {
|
|
@@ -736,45 +701,6 @@ function getRowForFeature(
|
|
|
736
701
|
return
|
|
737
702
|
}
|
|
738
703
|
|
|
739
|
-
function selectFeatureAndOpenWidget(
|
|
740
|
-
stateModel: LinearApolloDisplayMouseEvents,
|
|
741
|
-
feature: AnnotationFeature,
|
|
742
|
-
) {
|
|
743
|
-
if (stateModel.apolloDragging) {
|
|
744
|
-
return
|
|
745
|
-
}
|
|
746
|
-
stateModel.setSelectedFeature(feature)
|
|
747
|
-
const { session } = stateModel
|
|
748
|
-
const { apolloDataStore } = session
|
|
749
|
-
const { featureTypeOntology } = apolloDataStore.ontologyManager
|
|
750
|
-
if (!featureTypeOntology) {
|
|
751
|
-
throw new Error('featureTypeOntology is undefined')
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
let containsCDSOrExon = false
|
|
755
|
-
for (const [, child] of feature.children ?? []) {
|
|
756
|
-
if (
|
|
757
|
-
featureTypeOntology.isTypeOf(child.type, 'CDS') ||
|
|
758
|
-
featureTypeOntology.isTypeOf(child.type, 'exon')
|
|
759
|
-
) {
|
|
760
|
-
containsCDSOrExon = true
|
|
761
|
-
break
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (
|
|
765
|
-
(featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
766
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')) &&
|
|
767
|
-
containsCDSOrExon
|
|
768
|
-
) {
|
|
769
|
-
stateModel.showFeatureDetailsWidget(feature, [
|
|
770
|
-
'ApolloTranscriptDetails',
|
|
771
|
-
'apolloTranscriptDetails',
|
|
772
|
-
])
|
|
773
|
-
} else {
|
|
774
|
-
stateModel.showFeatureDetailsWidget(feature)
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
704
|
function onMouseDown(
|
|
779
705
|
stateModel: LinearApolloDisplay,
|
|
780
706
|
currentMousePosition: MousePositionWithFeature,
|
|
@@ -899,131 +825,6 @@ function getDraggableFeatureInfo(
|
|
|
899
825
|
return
|
|
900
826
|
}
|
|
901
827
|
|
|
902
|
-
function isTranscriptFeature(
|
|
903
|
-
feature: AnnotationFeature,
|
|
904
|
-
session: ApolloSessionModel,
|
|
905
|
-
): boolean {
|
|
906
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
907
|
-
if (!featureTypeOntology) {
|
|
908
|
-
throw new Error('featureTypeOntology is undefined')
|
|
909
|
-
}
|
|
910
|
-
return (
|
|
911
|
-
featureTypeOntology.isTypeOf(feature.type, 'transcript') ||
|
|
912
|
-
featureTypeOntology.isTypeOf(feature.type, 'pseudogenic_transcript')
|
|
913
|
-
)
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function isExonFeature(
|
|
917
|
-
feature: AnnotationFeature,
|
|
918
|
-
session: ApolloSessionModel,
|
|
919
|
-
): boolean {
|
|
920
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
921
|
-
if (!featureTypeOntology) {
|
|
922
|
-
throw new Error('featureTypeOntology is undefined')
|
|
923
|
-
}
|
|
924
|
-
return featureTypeOntology.isTypeOf(feature.type, 'exon')
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function isCDSFeature(
|
|
928
|
-
feature: AnnotationFeature,
|
|
929
|
-
session: ApolloSessionModel,
|
|
930
|
-
): boolean {
|
|
931
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
932
|
-
if (!featureTypeOntology) {
|
|
933
|
-
throw new Error('featureTypeOntology is undefined')
|
|
934
|
-
}
|
|
935
|
-
return featureTypeOntology.isTypeOf(feature.type, 'CDS')
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
interface AdjacentExons {
|
|
939
|
-
upstream: AnnotationFeature | undefined
|
|
940
|
-
downstream: AnnotationFeature | undefined
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function getAdjacentExons(
|
|
944
|
-
currentExon: AnnotationFeature,
|
|
945
|
-
display: LinearApolloDisplayMouseEvents,
|
|
946
|
-
mousePosition: MousePositionWithFeature,
|
|
947
|
-
session: ApolloSessionModel,
|
|
948
|
-
): AdjacentExons {
|
|
949
|
-
const lgv = getContainingView(
|
|
950
|
-
display as BaseDisplayModel,
|
|
951
|
-
) as unknown as LinearGenomeViewModel
|
|
952
|
-
|
|
953
|
-
// Genomic coords of current view
|
|
954
|
-
const viewGenomicLeft = mousePosition.bp - lgv.bpPerPx * mousePosition.x
|
|
955
|
-
const viewGenomicRight = viewGenomicLeft + lgv.coarseTotalBp
|
|
956
|
-
if (!currentExon.parent) {
|
|
957
|
-
return { upstream: undefined, downstream: undefined }
|
|
958
|
-
}
|
|
959
|
-
const transcript = currentExon.parent
|
|
960
|
-
if (!transcript.children) {
|
|
961
|
-
throw new Error(`Error getting children of ${transcript._id}`)
|
|
962
|
-
}
|
|
963
|
-
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
964
|
-
if (!featureTypeOntology) {
|
|
965
|
-
throw new Error('featureTypeOntology is undefined')
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
let exons = []
|
|
969
|
-
for (const [, child] of transcript.children) {
|
|
970
|
-
if (featureTypeOntology.isTypeOf(child.type, 'exon')) {
|
|
971
|
-
exons.push(child)
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
const adjacentExons: AdjacentExons = {
|
|
975
|
-
upstream: undefined,
|
|
976
|
-
downstream: undefined,
|
|
977
|
-
}
|
|
978
|
-
exons = exons.sort((a, b) => (a.min < b.min ? -1 : 1))
|
|
979
|
-
for (const exon of exons) {
|
|
980
|
-
if (exon.min > viewGenomicRight) {
|
|
981
|
-
adjacentExons.downstream = exon
|
|
982
|
-
break
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
exons = exons.sort((a, b) => (a.min > b.min ? -1 : 1))
|
|
986
|
-
for (const exon of exons) {
|
|
987
|
-
if (exon.max < viewGenomicLeft) {
|
|
988
|
-
adjacentExons.upstream = exon
|
|
989
|
-
break
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
if (transcript.strand === -1) {
|
|
993
|
-
const newUpstream = adjacentExons.downstream
|
|
994
|
-
adjacentExons.downstream = adjacentExons.upstream
|
|
995
|
-
adjacentExons.upstream = newUpstream
|
|
996
|
-
}
|
|
997
|
-
return adjacentExons
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
function getStreamIcon(
|
|
1001
|
-
strand: 1 | -1 | undefined,
|
|
1002
|
-
isUpstream: boolean,
|
|
1003
|
-
isFlipped: boolean | undefined,
|
|
1004
|
-
) {
|
|
1005
|
-
// This is the icon you would use for strand=1, downstream, straight
|
|
1006
|
-
// (non-flipped) view
|
|
1007
|
-
let icon = SkipNextRoundedIcon
|
|
1008
|
-
|
|
1009
|
-
if (strand === -1) {
|
|
1010
|
-
icon = SkipPreviousRoundedIcon
|
|
1011
|
-
}
|
|
1012
|
-
if (isUpstream) {
|
|
1013
|
-
icon =
|
|
1014
|
-
icon === SkipPreviousRoundedIcon
|
|
1015
|
-
? SkipNextRoundedIcon
|
|
1016
|
-
: SkipPreviousRoundedIcon
|
|
1017
|
-
}
|
|
1018
|
-
if (isFlipped) {
|
|
1019
|
-
icon =
|
|
1020
|
-
icon === SkipPreviousRoundedIcon
|
|
1021
|
-
? SkipNextRoundedIcon
|
|
1022
|
-
: SkipPreviousRoundedIcon
|
|
1023
|
-
}
|
|
1024
|
-
return icon
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
828
|
function getContextMenuItems(
|
|
1028
829
|
display: LinearApolloDisplayMouseEvents,
|
|
1029
830
|
mousePosition: MousePositionWithFeature,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ContentBlock } from '@jbrowse/core/util/blockTypes'
|
|
2
|
+
|
|
3
|
+
import { type LinearApolloDisplay } from '../stateModel'
|
|
4
|
+
|
|
5
|
+
export function getLeftPx(
|
|
6
|
+
display: LinearApolloDisplay,
|
|
7
|
+
feature: { max: number; min: number },
|
|
8
|
+
block: ContentBlock,
|
|
9
|
+
) {
|
|
10
|
+
const { lgv } = display
|
|
11
|
+
const { bpPerPx, offsetPx } = lgv
|
|
12
|
+
const blockLeftPx = block.offsetPx - offsetPx
|
|
13
|
+
const featureLeftBpDistanceFromBlockLeftBp = block.reversed
|
|
14
|
+
? block.end - feature.max
|
|
15
|
+
: feature.min - block.start
|
|
16
|
+
const featureLeftPxDistanceFromBlockLeftPx =
|
|
17
|
+
featureLeftBpDistanceFromBlockLeftBp / bpPerPx
|
|
18
|
+
return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx
|
|
19
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import { type Frame, getFrame } from '@jbrowse/core/util'
|
|
3
|
+
import { type BlockSet, type ContentBlock } from '@jbrowse/core/util/blockTypes'
|
|
4
|
+
import { type Theme } from '@mui/material'
|
|
5
|
+
|
|
6
|
+
import { type ApolloSessionModel, type HoveredFeature } from '../session'
|
|
7
|
+
|
|
8
|
+
function getSeqRow(
|
|
9
|
+
strand: 1 | -1 | undefined,
|
|
10
|
+
bpPerPx: number,
|
|
11
|
+
): number | undefined {
|
|
12
|
+
if (bpPerPx > 1 || strand === undefined) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
return strand === 1 ? 3 : 4
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getTranslationRow(frame: Frame, bpPerPx: number) {
|
|
19
|
+
const offset = bpPerPx <= 1 ? 2 : 0
|
|
20
|
+
switch (frame) {
|
|
21
|
+
case 3: {
|
|
22
|
+
return 0
|
|
23
|
+
}
|
|
24
|
+
case 2: {
|
|
25
|
+
return 1
|
|
26
|
+
}
|
|
27
|
+
case 1: {
|
|
28
|
+
return 2
|
|
29
|
+
}
|
|
30
|
+
case -1: {
|
|
31
|
+
return 3 + offset
|
|
32
|
+
}
|
|
33
|
+
case -2: {
|
|
34
|
+
return 4 + offset
|
|
35
|
+
}
|
|
36
|
+
case -3: {
|
|
37
|
+
return 5 + offset
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getLeftPx(
|
|
43
|
+
feature: { min: number; max: number },
|
|
44
|
+
bpPerPx: number,
|
|
45
|
+
offsetPx: number,
|
|
46
|
+
block: ContentBlock,
|
|
47
|
+
) {
|
|
48
|
+
const blockLeftPx = block.offsetPx - offsetPx
|
|
49
|
+
const featureLeftBpDistanceFromBlockLeftBp = block.reversed
|
|
50
|
+
? block.end - feature.max
|
|
51
|
+
: feature.min - block.start
|
|
52
|
+
const featureLeftPxDistanceFromBlockLeftPx =
|
|
53
|
+
featureLeftBpDistanceFromBlockLeftBp / bpPerPx
|
|
54
|
+
return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function fillAndStrokeRect(
|
|
58
|
+
ctx: CanvasRenderingContext2D,
|
|
59
|
+
left: number,
|
|
60
|
+
top: number,
|
|
61
|
+
width: number,
|
|
62
|
+
height: number,
|
|
63
|
+
theme: Theme,
|
|
64
|
+
selected = false,
|
|
65
|
+
) {
|
|
66
|
+
ctx.fillStyle = selected
|
|
67
|
+
? theme.palette.action.disabled
|
|
68
|
+
: theme.palette.action.focus
|
|
69
|
+
ctx.fillRect(left, top, width, height)
|
|
70
|
+
ctx.strokeStyle = selected
|
|
71
|
+
? theme.palette.text.secondary
|
|
72
|
+
: theme.palette.text.primary
|
|
73
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
74
|
+
ctx.strokeRect(left, top, width, height)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function drawHighlight(
|
|
78
|
+
ctx: CanvasRenderingContext2D,
|
|
79
|
+
feature: AnnotationFeature,
|
|
80
|
+
bpPerPx: number,
|
|
81
|
+
offsetPx: number,
|
|
82
|
+
rowHeight: number,
|
|
83
|
+
block: ContentBlock,
|
|
84
|
+
theme: Theme,
|
|
85
|
+
selected = false,
|
|
86
|
+
) {
|
|
87
|
+
const row = getSeqRow(feature.strand, bpPerPx)
|
|
88
|
+
if (!row) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
const left = getLeftPx(feature, bpPerPx, offsetPx, block)
|
|
92
|
+
const width = feature.length / bpPerPx
|
|
93
|
+
const top = row * rowHeight
|
|
94
|
+
fillAndStrokeRect(ctx, left, top, width, rowHeight, theme, selected)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function drawCDSHighlight(
|
|
98
|
+
ctx: CanvasRenderingContext2D,
|
|
99
|
+
feature: AnnotationFeature,
|
|
100
|
+
bpPerPx: number,
|
|
101
|
+
offsetPx: number,
|
|
102
|
+
rowHeight: number,
|
|
103
|
+
block: ContentBlock,
|
|
104
|
+
theme: Theme,
|
|
105
|
+
selected = false,
|
|
106
|
+
) {
|
|
107
|
+
const parentFeature = feature.parent
|
|
108
|
+
if (!parentFeature) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
const cdsLocs = parentFeature.cdsLocations.find((loc) => {
|
|
112
|
+
const min = loc.at(feature.strand === 1 ? 0 : -1)?.min
|
|
113
|
+
const max = loc.at(feature.strand === 1 ? -1 : 0)?.max
|
|
114
|
+
return feature.min === min && feature.max === max
|
|
115
|
+
})
|
|
116
|
+
if (!cdsLocs) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
for (const loc of cdsLocs) {
|
|
120
|
+
const frame = getFrame(loc.min, loc.max, feature.strand ?? 1, loc.phase)
|
|
121
|
+
const row = getTranslationRow(frame, bpPerPx)
|
|
122
|
+
const left = getLeftPx(loc, bpPerPx, offsetPx, block)
|
|
123
|
+
const top = row * rowHeight
|
|
124
|
+
const width = (loc.max - loc.min) / bpPerPx
|
|
125
|
+
fillAndStrokeRect(ctx, left, top, width, rowHeight, theme, selected)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function drawSequenceOverlay(
|
|
130
|
+
canvas: HTMLCanvasElement,
|
|
131
|
+
ctx: CanvasRenderingContext2D,
|
|
132
|
+
hoveredFeature: HoveredFeature | undefined,
|
|
133
|
+
selectedFeature: AnnotationFeature | undefined,
|
|
134
|
+
rowHeight: number,
|
|
135
|
+
theme: Theme,
|
|
136
|
+
session: ApolloSessionModel,
|
|
137
|
+
bpPerPx: number,
|
|
138
|
+
offsetPx: number,
|
|
139
|
+
dynamicBlocks: BlockSet,
|
|
140
|
+
) {
|
|
141
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
142
|
+
if (!featureTypeOntology) {
|
|
143
|
+
throw new Error('featureTypeOntology is undefined')
|
|
144
|
+
}
|
|
145
|
+
for (const block of dynamicBlocks.contentBlocks) {
|
|
146
|
+
ctx.save()
|
|
147
|
+
ctx.beginPath()
|
|
148
|
+
const blockLeftPx = block.offsetPx - offsetPx
|
|
149
|
+
ctx.rect(blockLeftPx, 0, block.widthPx, canvas.height)
|
|
150
|
+
ctx.clip()
|
|
151
|
+
for (const feature of [
|
|
152
|
+
selectedFeature,
|
|
153
|
+
hoveredFeature?.feature,
|
|
154
|
+
].filter<AnnotationFeature>((f) => f !== undefined)) {
|
|
155
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'CDS')) {
|
|
156
|
+
drawCDSHighlight(
|
|
157
|
+
ctx,
|
|
158
|
+
feature,
|
|
159
|
+
bpPerPx,
|
|
160
|
+
offsetPx,
|
|
161
|
+
rowHeight,
|
|
162
|
+
block,
|
|
163
|
+
theme,
|
|
164
|
+
true,
|
|
165
|
+
)
|
|
166
|
+
} else {
|
|
167
|
+
drawHighlight(
|
|
168
|
+
ctx,
|
|
169
|
+
feature,
|
|
170
|
+
bpPerPx,
|
|
171
|
+
offsetPx,
|
|
172
|
+
rowHeight,
|
|
173
|
+
block,
|
|
174
|
+
theme,
|
|
175
|
+
true,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
ctx.restore()
|
|
180
|
+
}
|
|
181
|
+
}
|