@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 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 +11212 -10483
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
- 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 +7726 -9014
- 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 +18 -18
- package/src/ApolloInternetAccount/model.ts +123 -70
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +36 -14
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +34 -38
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +30 -21
- package/src/components/AddRefSeqAliases.tsx +64 -50
- package/src/components/CopyFeature.tsx +4 -2
- package/src/components/CreateApolloAnnotation.tsx +22 -9
- package/src/components/DeleteAssembly.tsx +3 -10
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +3 -10
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermAutocomplete.tsx +1 -8
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +2 -8
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- package/src/index.ts +53 -115
- package/src/makeDisplayComponent.tsx +9 -14
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/ClientDataStore.ts +32 -14
- package/src/session/session.ts +159 -121
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +329 -0
- package/src/util/loadAssemblyIntoClient.ts +3 -2
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { defaultCodonTable, getFrame, revcom } from '@jbrowse/core/util'
|
|
2
|
+
import { type BlockSet } from '@jbrowse/core/util/blockTypes'
|
|
3
|
+
import { type Theme } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
import { type ApolloSessionModel } from '../session'
|
|
6
|
+
|
|
7
|
+
function colorCode(letter: string, theme: Theme) {
|
|
8
|
+
const letterUpper = letter.toUpperCase()
|
|
9
|
+
if (
|
|
10
|
+
letterUpper === 'A' ||
|
|
11
|
+
letterUpper === 'C' ||
|
|
12
|
+
letterUpper === 'G' ||
|
|
13
|
+
letterUpper === 'T'
|
|
14
|
+
) {
|
|
15
|
+
return theme.palette.bases[letterUpper].main.toString()
|
|
16
|
+
}
|
|
17
|
+
return 'lightgray'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function codonColorCode(letter: string, theme: Theme, highContrast?: boolean) {
|
|
21
|
+
if (letter === 'M') {
|
|
22
|
+
return theme.palette.startCodon
|
|
23
|
+
}
|
|
24
|
+
if (letter === '*') {
|
|
25
|
+
return highContrast ? theme.palette.text.primary : theme.palette.stopCodon
|
|
26
|
+
}
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function drawLetter(
|
|
31
|
+
seqTrackctx: CanvasRenderingContext2D,
|
|
32
|
+
left: number,
|
|
33
|
+
top: number,
|
|
34
|
+
width: number,
|
|
35
|
+
letter: string,
|
|
36
|
+
) {
|
|
37
|
+
const fontSize = Math.min(width, 10)
|
|
38
|
+
seqTrackctx.fillStyle = '#000'
|
|
39
|
+
seqTrackctx.font = `${fontSize}px`
|
|
40
|
+
const textWidth = seqTrackctx.measureText(letter).width
|
|
41
|
+
const textX = left + (width - textWidth) / 2
|
|
42
|
+
seqTrackctx.fillText(letter, textX, top + 10)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function drawTranslationFrameBackgrounds(
|
|
46
|
+
canvas: HTMLCanvasElement,
|
|
47
|
+
ctx: CanvasRenderingContext2D,
|
|
48
|
+
bpPerPx: number,
|
|
49
|
+
theme: Theme,
|
|
50
|
+
dynamicBlocks: BlockSet,
|
|
51
|
+
highContrast: boolean,
|
|
52
|
+
sequenceRowHeight: number,
|
|
53
|
+
) {
|
|
54
|
+
const frames =
|
|
55
|
+
bpPerPx <= 1 ? [3, 2, 1, 0, 0, -1, -2, -3] : [3, 2, 1, -1, -2, -3]
|
|
56
|
+
for (const [idx, frame] of frames.entries()) {
|
|
57
|
+
const frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
58
|
+
if (!frameColor) {
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
const top = idx * sequenceRowHeight
|
|
62
|
+
const { offsetPx } = dynamicBlocks
|
|
63
|
+
const left = Math.max(0, -offsetPx)
|
|
64
|
+
const width = dynamicBlocks.totalWidthPx
|
|
65
|
+
ctx.fillStyle = highContrast ? theme.palette.background.default : frameColor
|
|
66
|
+
ctx.fillRect(left, top, width, sequenceRowHeight)
|
|
67
|
+
if (highContrast) {
|
|
68
|
+
// eslint-disable-next-line prefer-destructuring
|
|
69
|
+
ctx.strokeStyle = theme.palette.grey[200]
|
|
70
|
+
ctx.strokeRect(left, top, width, sequenceRowHeight)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// allows inter-region padding lines to show through
|
|
74
|
+
for (const block of dynamicBlocks.getBlocks()) {
|
|
75
|
+
if (block.type === 'InterRegionPaddingBlock') {
|
|
76
|
+
const left = block.offsetPx - dynamicBlocks.offsetPx
|
|
77
|
+
ctx.clearRect(left, 0, block.widthPx, canvas.height)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function drawBase(
|
|
83
|
+
ctx: CanvasRenderingContext2D,
|
|
84
|
+
base: string,
|
|
85
|
+
index: number,
|
|
86
|
+
leftPx: number,
|
|
87
|
+
bpPerPx: number,
|
|
88
|
+
rowHeight: number,
|
|
89
|
+
theme: Theme,
|
|
90
|
+
) {
|
|
91
|
+
const width = 1 / bpPerPx
|
|
92
|
+
if (width < 1) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
const left = leftPx + index / bpPerPx
|
|
96
|
+
const strands = [-1, 1] as const
|
|
97
|
+
for (const strand of strands) {
|
|
98
|
+
const top = (strand === 1 ? 3 : 4) * rowHeight
|
|
99
|
+
const baseCode = strand === 1 ? base : revcom(base)
|
|
100
|
+
ctx.fillStyle = colorCode(baseCode, theme)
|
|
101
|
+
ctx.fillRect(left, top, width, rowHeight)
|
|
102
|
+
if (1 / bpPerPx >= 12) {
|
|
103
|
+
ctx.strokeStyle = theme.palette.text.disabled
|
|
104
|
+
ctx.strokeRect(left, top, width, rowHeight)
|
|
105
|
+
drawLetter(ctx, left, top, width, baseCode)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function drawCodon(
|
|
111
|
+
ctx: CanvasRenderingContext2D,
|
|
112
|
+
codon: string,
|
|
113
|
+
leftPx: number,
|
|
114
|
+
index: number,
|
|
115
|
+
theme: Theme,
|
|
116
|
+
highContrast: boolean,
|
|
117
|
+
bpPerPx: number,
|
|
118
|
+
bp: number,
|
|
119
|
+
rowHeight: number,
|
|
120
|
+
showStartCodons: boolean,
|
|
121
|
+
showStopCodons: boolean,
|
|
122
|
+
) {
|
|
123
|
+
const frameOffsets = (
|
|
124
|
+
bpPerPx <= 1 ? [0, 2, 1, 0, 7, 6, 5] : [0, 2, 1, 0, 5, 4, 3]
|
|
125
|
+
).map((b) => b * rowHeight)
|
|
126
|
+
const strands = [-1, 1] as const
|
|
127
|
+
for (const strand of strands) {
|
|
128
|
+
const frame = getFrame(bp, bp + 3, strand, 0)
|
|
129
|
+
const top = frameOffsets.at(frame)
|
|
130
|
+
if (top === undefined) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
const left = Math.round(leftPx + index / bpPerPx)
|
|
134
|
+
const width = Math.round(3 / bpPerPx)
|
|
135
|
+
const codonCode = strand === 1 ? codon : revcom(codon)
|
|
136
|
+
const aminoAcidCode =
|
|
137
|
+
defaultCodonTable[codonCode as keyof typeof defaultCodonTable]
|
|
138
|
+
const fillColor = codonColorCode(aminoAcidCode, theme, highContrast)
|
|
139
|
+
if (
|
|
140
|
+
fillColor &&
|
|
141
|
+
((showStopCodons && aminoAcidCode == '*') ||
|
|
142
|
+
(showStartCodons && aminoAcidCode != '*'))
|
|
143
|
+
) {
|
|
144
|
+
ctx.fillStyle = fillColor
|
|
145
|
+
ctx.fillRect(left, top, width, rowHeight)
|
|
146
|
+
}
|
|
147
|
+
if (1 / bpPerPx >= 4) {
|
|
148
|
+
ctx.strokeStyle = theme.palette.text.disabled
|
|
149
|
+
ctx.strokeRect(left, top, width, rowHeight)
|
|
150
|
+
drawLetter(ctx, left, top, width, aminoAcidCode)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function drawSequenceTrack(
|
|
156
|
+
canvas: HTMLCanvasElement,
|
|
157
|
+
theme: Theme,
|
|
158
|
+
bpPerPx: number,
|
|
159
|
+
offsetPx: number,
|
|
160
|
+
dynamicBlocks: BlockSet,
|
|
161
|
+
highContrast: boolean,
|
|
162
|
+
showStartCodons: boolean,
|
|
163
|
+
showStopCodons: boolean,
|
|
164
|
+
sequenceRowHeight: number,
|
|
165
|
+
session: ApolloSessionModel,
|
|
166
|
+
) {
|
|
167
|
+
const ctx = canvas.getContext('2d')
|
|
168
|
+
if (!ctx) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
172
|
+
|
|
173
|
+
drawTranslationFrameBackgrounds(
|
|
174
|
+
canvas,
|
|
175
|
+
ctx,
|
|
176
|
+
bpPerPx,
|
|
177
|
+
theme,
|
|
178
|
+
dynamicBlocks,
|
|
179
|
+
highContrast,
|
|
180
|
+
sequenceRowHeight,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const { apolloDataStore } = session
|
|
184
|
+
for (const block of dynamicBlocks.contentBlocks) {
|
|
185
|
+
const assembly = apolloDataStore.assemblies.get(block.assemblyName)
|
|
186
|
+
const ref = assembly?.getByRefName(block.refName)
|
|
187
|
+
const roundedStart = Math.floor(block.start)
|
|
188
|
+
const roundedEnd = Math.ceil(block.end)
|
|
189
|
+
let seq = ref?.getSequence(roundedStart, roundedEnd)
|
|
190
|
+
if (!seq) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
seq = seq.toUpperCase()
|
|
194
|
+
const baseOffsetPx = (block.start - roundedStart) / bpPerPx
|
|
195
|
+
const seqLeftPx = Math.round(block.offsetPx - offsetPx - baseOffsetPx)
|
|
196
|
+
for (let i = 0; i < seq.length; i++) {
|
|
197
|
+
const bp = roundedStart + i
|
|
198
|
+
const codon = seq.slice(i, i + 3)
|
|
199
|
+
drawBase(ctx, seq[i], i, seqLeftPx, bpPerPx, sequenceRowHeight, theme)
|
|
200
|
+
if (codon.length !== 3) {
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
drawCodon(
|
|
204
|
+
ctx,
|
|
205
|
+
codon,
|
|
206
|
+
seqLeftPx,
|
|
207
|
+
i,
|
|
208
|
+
theme,
|
|
209
|
+
highContrast,
|
|
210
|
+
bpPerPx,
|
|
211
|
+
bp,
|
|
212
|
+
sequenceRowHeight,
|
|
213
|
+
showStartCodons,
|
|
214
|
+
showStopCodons,
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
6
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
7
|
+
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
8
|
+
import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
|
|
9
|
+
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
|
|
10
|
+
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes'
|
|
11
|
+
import {
|
|
12
|
+
type AbstractSessionModel,
|
|
13
|
+
getContainingView,
|
|
14
|
+
getSession,
|
|
15
|
+
} from '@jbrowse/core/util'
|
|
16
|
+
import { getParentRenderProps } from '@jbrowse/core/util/tracks'
|
|
17
|
+
// import type LinearGenomeViewPlugin from '@jbrowse/plugin-linear-genome-view'
|
|
18
|
+
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
19
|
+
import { autorun } from 'mobx'
|
|
20
|
+
import { addDisposer, getRoot, types } from 'mobx-state-tree'
|
|
21
|
+
|
|
22
|
+
import { type ApolloInternetAccountModel } from '../../ApolloInternetAccount/model'
|
|
23
|
+
import { type ApolloSessionModel, type HoveredFeature } from '../../session'
|
|
24
|
+
import { type ApolloRootModel } from '../../types'
|
|
25
|
+
|
|
26
|
+
const minDisplayHeight = 20
|
|
27
|
+
|
|
28
|
+
export function baseModelFactory(
|
|
29
|
+
_pluginManager: PluginManager,
|
|
30
|
+
configSchema: AnyConfigurationSchemaType,
|
|
31
|
+
) {
|
|
32
|
+
return BaseDisplay.named('BaseLinearApolloReferenceSequenceDisplay')
|
|
33
|
+
.props({
|
|
34
|
+
type: types.literal('LinearApolloReferenceSequenceDisplay'),
|
|
35
|
+
configuration: ConfigurationReference(configSchema),
|
|
36
|
+
showStartCodons: false,
|
|
37
|
+
showStopCodons: true,
|
|
38
|
+
highContrast: false,
|
|
39
|
+
heightPreConfig: types.maybe(
|
|
40
|
+
types.refinement(
|
|
41
|
+
'displayHeight',
|
|
42
|
+
types.number,
|
|
43
|
+
(n) => n >= minDisplayHeight,
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
sequenceRowHeight: 15,
|
|
47
|
+
})
|
|
48
|
+
.views((self) => {
|
|
49
|
+
const { configuration, renderProps: superRenderProps } = self
|
|
50
|
+
return {
|
|
51
|
+
renderProps() {
|
|
52
|
+
return {
|
|
53
|
+
...superRenderProps(),
|
|
54
|
+
...getParentRenderProps(self),
|
|
55
|
+
config: configuration.renderer,
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.views((self) => ({
|
|
61
|
+
get lgv() {
|
|
62
|
+
return getContainingView(self) as unknown as LinearGenomeViewModel
|
|
63
|
+
},
|
|
64
|
+
}))
|
|
65
|
+
.views((self) => ({
|
|
66
|
+
get rendererTypeName() {
|
|
67
|
+
return self.configuration.renderer.type
|
|
68
|
+
},
|
|
69
|
+
get session() {
|
|
70
|
+
return getSession(self) as unknown as ApolloSessionModel
|
|
71
|
+
},
|
|
72
|
+
get regions() {
|
|
73
|
+
const regions = self.lgv.dynamicBlocks.contentBlocks.map(
|
|
74
|
+
({ assemblyName, end, refName, start }) => ({
|
|
75
|
+
assemblyName,
|
|
76
|
+
refName,
|
|
77
|
+
start: Math.round(start),
|
|
78
|
+
end: Math.round(end),
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
81
|
+
return regions
|
|
82
|
+
},
|
|
83
|
+
regionCannotBeRendered(/* region */) {
|
|
84
|
+
if (self.lgv && self.lgv.bpPerPx >= 3) {
|
|
85
|
+
return 'Zoom in to see sequence'
|
|
86
|
+
}
|
|
87
|
+
return
|
|
88
|
+
},
|
|
89
|
+
}))
|
|
90
|
+
.views((self) => ({
|
|
91
|
+
get apolloInternetAccount() {
|
|
92
|
+
const [region] = self.regions
|
|
93
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(self)
|
|
94
|
+
const { assemblyName } = region
|
|
95
|
+
const { assemblyManager } =
|
|
96
|
+
self.session as unknown as AbstractSessionModel
|
|
97
|
+
const assembly = assemblyManager.get(assemblyName)
|
|
98
|
+
if (!assembly) {
|
|
99
|
+
throw new Error(`No assembly found with name ${assemblyName}`)
|
|
100
|
+
}
|
|
101
|
+
const { internetAccountConfigId } = getConf(assembly, [
|
|
102
|
+
'sequence',
|
|
103
|
+
'metadata',
|
|
104
|
+
]) as { internetAccountConfigId: string }
|
|
105
|
+
return internetAccounts.find(
|
|
106
|
+
(ia) => getConf(ia, 'internetAccountId') === internetAccountConfigId,
|
|
107
|
+
) as ApolloInternetAccountModel | undefined
|
|
108
|
+
},
|
|
109
|
+
get changeManager() {
|
|
110
|
+
return (self.session as unknown as ApolloSessionModel).apolloDataStore
|
|
111
|
+
.changeManager
|
|
112
|
+
},
|
|
113
|
+
getAssemblyId(assemblyName: string) {
|
|
114
|
+
const { assemblyManager } =
|
|
115
|
+
self.session as unknown as AbstractSessionModel
|
|
116
|
+
const assembly = assemblyManager.get(assemblyName)
|
|
117
|
+
if (!assembly) {
|
|
118
|
+
throw new Error(`Could not find assembly named ${assemblyName}`)
|
|
119
|
+
}
|
|
120
|
+
return assembly.name
|
|
121
|
+
},
|
|
122
|
+
get selectedFeature(): AnnotationFeature | undefined {
|
|
123
|
+
return (self.session as unknown as ApolloSessionModel)
|
|
124
|
+
.apolloSelectedFeature
|
|
125
|
+
},
|
|
126
|
+
get hoveredFeature(): HoveredFeature | undefined {
|
|
127
|
+
return (self.session as unknown as ApolloSessionModel)
|
|
128
|
+
.apolloHoveredFeature
|
|
129
|
+
},
|
|
130
|
+
get height() {
|
|
131
|
+
const { sequenceRowHeight } = self
|
|
132
|
+
return self.lgv.bpPerPx <= 1
|
|
133
|
+
? sequenceRowHeight * 8
|
|
134
|
+
: sequenceRowHeight * 6
|
|
135
|
+
},
|
|
136
|
+
}))
|
|
137
|
+
.volatile(() => ({
|
|
138
|
+
scrollTop: 0,
|
|
139
|
+
}))
|
|
140
|
+
.actions((self) => ({
|
|
141
|
+
setScrollTop(scrollTop: number) {
|
|
142
|
+
self.scrollTop = scrollTop
|
|
143
|
+
},
|
|
144
|
+
setHeight(displayHeight: number) {
|
|
145
|
+
self.heightPreConfig = Math.max(displayHeight, minDisplayHeight)
|
|
146
|
+
return self.height
|
|
147
|
+
},
|
|
148
|
+
resizeHeight(distance: number) {
|
|
149
|
+
const oldHeight = self.height
|
|
150
|
+
const newHeight = this.setHeight(self.height + distance)
|
|
151
|
+
return newHeight - oldHeight
|
|
152
|
+
},
|
|
153
|
+
toggleShowStartCodons() {
|
|
154
|
+
self.showStartCodons = !self.showStartCodons
|
|
155
|
+
},
|
|
156
|
+
toggleShowStopCodons() {
|
|
157
|
+
self.showStopCodons = !self.showStopCodons
|
|
158
|
+
},
|
|
159
|
+
toggleHighContrast() {
|
|
160
|
+
self.highContrast = !self.highContrast
|
|
161
|
+
},
|
|
162
|
+
}))
|
|
163
|
+
.views((self) => {
|
|
164
|
+
const { trackMenuItems: superTrackMenuItems } = self
|
|
165
|
+
return {
|
|
166
|
+
trackMenuItems() {
|
|
167
|
+
const { showStartCodons, showStopCodons, highContrast } = self
|
|
168
|
+
return [
|
|
169
|
+
...superTrackMenuItems(),
|
|
170
|
+
{
|
|
171
|
+
type: 'subMenu',
|
|
172
|
+
label: 'Appearance',
|
|
173
|
+
subMenu: [
|
|
174
|
+
{
|
|
175
|
+
label: 'Show start codons',
|
|
176
|
+
type: 'checkbox',
|
|
177
|
+
checked: showStartCodons,
|
|
178
|
+
onClick: () => {
|
|
179
|
+
self.toggleShowStartCodons()
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
label: 'Show stop codons',
|
|
184
|
+
type: 'checkbox',
|
|
185
|
+
checked: showStopCodons,
|
|
186
|
+
onClick: () => {
|
|
187
|
+
self.toggleShowStopCodons()
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
label: 'Use high contrast colors',
|
|
192
|
+
type: 'checkbox',
|
|
193
|
+
checked: highContrast,
|
|
194
|
+
onClick: () => {
|
|
195
|
+
self.toggleHighContrast()
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
.actions((self) => ({
|
|
205
|
+
afterAttach() {
|
|
206
|
+
addDisposer(
|
|
207
|
+
self,
|
|
208
|
+
autorun(
|
|
209
|
+
() => {
|
|
210
|
+
if (!self.lgv.initialized || self.regionCannotBeRendered()) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
if (self.lgv.bpPerPx <= 3) {
|
|
214
|
+
void (
|
|
215
|
+
self.session as unknown as ApolloSessionModel
|
|
216
|
+
).apolloDataStore.loadRefSeq(self.regions)
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'LinearApolloReferenceSequenceDisplayLoadFeatures',
|
|
221
|
+
delay: 1000,
|
|
222
|
+
},
|
|
223
|
+
),
|
|
224
|
+
)
|
|
225
|
+
},
|
|
226
|
+
}))
|
|
227
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
|
|
3
|
+
import { type Instance } from 'mobx-state-tree'
|
|
4
|
+
|
|
5
|
+
import { renderingModelFactory } from './rendering'
|
|
6
|
+
|
|
7
|
+
export function stateModelFactory(
|
|
8
|
+
pluginManager: PluginManager,
|
|
9
|
+
configSchema: AnyConfigurationSchemaType,
|
|
10
|
+
) {
|
|
11
|
+
// TODO: this needs to be refactored so that the final composition of the
|
|
12
|
+
// state model mixins happens here in one central place
|
|
13
|
+
return renderingModelFactory(pluginManager, configSchema).named(
|
|
14
|
+
'LinearApolloReferenceSequenceDisplay',
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type LinearApolloReferenceSequenceDisplayStateModel = ReturnType<
|
|
19
|
+
typeof stateModelFactory
|
|
20
|
+
>
|
|
21
|
+
// eslint disable because of
|
|
22
|
+
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
24
|
+
export interface LinearApolloReferenceSequenceDisplay
|
|
25
|
+
extends Instance<LinearApolloReferenceSequenceDisplayStateModel> {}
|