Skip to content

Commit 2398922

Browse files
authored
Render start/stop codons on LASFD (#731)
* toggles * rendering of high-contrast, feature-label-aware, start/stop codons on LASFD * tie loading of refSeq to zoomThreshold level rather than hard-coded "3" * make use of util functions * Ensure codon rendering occurs after feature rendering, to avoid codons getting covered by features
1 parent 2523f16 commit 2398922

File tree

4 files changed

+163
-28
lines changed

4 files changed

+163
-28
lines changed

packages/jbrowse-plugin-apollo/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,7 @@ import { type BlockSet } from '@jbrowse/core/util/blockTypes'
33
import { type Theme } from '@mui/material'
44

55
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-
}
6+
import { codonColorCode, colorCode } from '../util/displayUtils'
297

308
function drawLetter(
319
seqTrackctx: CanvasRenderingContext2D,

packages/jbrowse-plugin-apollo/src/LinearApolloSixFrameDisplay/stateModel/base.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export function baseModelFactory(
3939
graphical: true,
4040
table: false,
4141
showFeatureLabels: true,
42+
showStartCodons: false,
43+
showStopCodons: true,
4244
showCheckResults: true,
4345
zoomThreshold: 200,
4446
heightPreConfig: types.maybe(
@@ -179,6 +181,12 @@ export function baseModelFactory(
179181
toggleShowFeatureLabels() {
180182
self.showFeatureLabels = !self.showFeatureLabels
181183
},
184+
toggleShowStartCodons() {
185+
self.showStartCodons = !self.showStartCodons
186+
},
187+
toggleShowStopCodons() {
188+
self.showStopCodons = !self.showStopCodons
189+
},
182190
toggleShowCheckResults() {
183191
self.showCheckResults = !self.showCheckResults
184192
},
@@ -193,7 +201,14 @@ export function baseModelFactory(
193201
const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
194202
return {
195203
trackMenuItems() {
196-
const { graphical, table, showFeatureLabels, showCheckResults } = self
204+
const {
205+
graphical,
206+
table,
207+
showFeatureLabels,
208+
showStartCodons,
209+
showStopCodons,
210+
showCheckResults,
211+
} = self
197212
return [
198213
...superTrackMenuItems(),
199214
{
@@ -232,6 +247,22 @@ export function baseModelFactory(
232247
self.toggleShowFeatureLabels()
233248
},
234249
},
250+
{
251+
label: 'Show start codons',
252+
type: 'checkbox',
253+
checked: showStartCodons,
254+
onClick: () => {
255+
self.toggleShowStartCodons()
256+
},
257+
},
258+
{
259+
label: 'Show stop codons',
260+
type: 'checkbox',
261+
checked: showStopCodons,
262+
onClick: () => {
263+
self.toggleShowStopCodons()
264+
},
265+
},
235266
{
236267
label: 'Check Results',
237268
type: 'checkbox',
@@ -326,7 +357,7 @@ export function baseModelFactory(
326357
void (
327358
self.session as unknown as ApolloSessionModel
328359
).apolloDataStore.loadFeatures(self.regions)
329-
if (self.lgv.bpPerPx <= 3) {
360+
if (self.lgv.bpPerPx <= self.zoomThreshold) {
330361
void (
331362
self.session as unknown as ApolloSessionModel
332363
).apolloDataStore.loadRefSeq(self.regions)

packages/jbrowse-plugin-apollo/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,62 @@
11
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
22
import type PluginManager from '@jbrowse/core/PluginManager'
33
import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
4-
import { doesIntersect2 } from '@jbrowse/core/util'
4+
import {
5+
defaultCodonTable,
6+
doesIntersect2,
7+
getFrame,
8+
revcom,
9+
} from '@jbrowse/core/util'
510
import { type Theme, createTheme } from '@mui/material'
611
import { autorun } from 'mobx'
712
import { type Instance, addDisposer, types } from 'mobx-state-tree'
813

914
import { type ApolloSessionModel } from '../../session'
15+
import { codonColorCode } from '../../util/displayUtils'
1016

1117
import { layoutsModelFactory } from './layouts'
1218

19+
function drawCodon(
20+
ctx: CanvasRenderingContext2D,
21+
codon: string,
22+
leftPx: number,
23+
index: number,
24+
theme: Theme,
25+
highContrast: boolean,
26+
bpPerPx: number,
27+
bp: number,
28+
rowHeight: number,
29+
showFeatureLabels: boolean,
30+
showStartCodons: boolean,
31+
showStopCodons: boolean,
32+
) {
33+
const frameOffsets = (
34+
showFeatureLabels ? [0, 4, 2, 0, 14, 12, 10] : [0, 2, 1, 0, 7, 6, 5]
35+
).map((b) => b * rowHeight)
36+
const strands = [-1, 1] as const
37+
for (const strand of strands) {
38+
const frame = getFrame(bp, bp + 3, strand, 0)
39+
const top = frameOffsets.at(frame)
40+
if (top === undefined) {
41+
continue
42+
}
43+
const left = Math.round(leftPx + index / bpPerPx)
44+
const width = Math.round(3 / bpPerPx) === 0 ? 1 : Math.round(3 / bpPerPx)
45+
const codonCode = strand === 1 ? codon : revcom(codon)
46+
const aminoAcidCode =
47+
defaultCodonTable[codonCode as keyof typeof defaultCodonTable]
48+
const fillColor = codonColorCode(aminoAcidCode, theme, highContrast)
49+
if (
50+
fillColor &&
51+
((showStopCodons && aminoAcidCode == '*') ||
52+
(showStartCodons && aminoAcidCode != '*'))
53+
) {
54+
ctx.fillStyle = fillColor
55+
ctx.fillRect(left, top, width, rowHeight)
56+
}
57+
}
58+
}
59+
1360
export function renderingModelFactory(
1461
pluginManager: PluginManager,
1562
configSchema: AnyConfigurationSchemaType,
@@ -135,17 +182,29 @@ export function renderingModelFactory(
135182
self,
136183
autorun(
137184
() => {
138-
const { canvas, featureLayouts, featuresHeight, lgv } = self
185+
const {
186+
apolloRowHeight,
187+
canvas,
188+
featureLayouts,
189+
featuresHeight,
190+
lgv,
191+
session,
192+
theme,
193+
showFeatureLabels,
194+
showStartCodons,
195+
showStopCodons,
196+
} = self
139197
if (!lgv.initialized || self.regionCannotBeRendered()) {
140198
return
141199
}
142-
const { displayedRegions, dynamicBlocks } = lgv
200+
const { bpPerPx, offsetPx, displayedRegions, dynamicBlocks } = lgv
143201

144202
const ctx = canvas?.getContext('2d')
145203
if (!ctx) {
146204
return
147205
}
148206
ctx.clearRect(0, 0, dynamicBlocks.totalWidthPx, featuresHeight)
207+
149208
for (const [idx, featureLayout] of featureLayouts.entries()) {
150209
const displayedRegion = displayedRegions[idx]
151210
for (const [row, featureLayoutRow] of featureLayout.entries()) {
@@ -171,6 +230,45 @@ export function renderingModelFactory(
171230
}
172231
}
173232
}
233+
234+
if (showStartCodons || showStopCodons) {
235+
const { apolloDataStore } = session
236+
for (const block of dynamicBlocks.contentBlocks) {
237+
const assembly = apolloDataStore.assemblies.get(
238+
block.assemblyName,
239+
)
240+
const ref = assembly?.getByRefName(block.refName)
241+
const roundedStart = Math.floor(block.start)
242+
const roundedEnd = Math.ceil(block.end)
243+
let seq = ref?.getSequence(roundedStart, roundedEnd)
244+
if (!seq) {
245+
break
246+
}
247+
seq = seq.toUpperCase()
248+
const baseOffsetPx = (block.start - roundedStart) / bpPerPx
249+
const seqLeftPx = Math.round(
250+
block.offsetPx - offsetPx - baseOffsetPx,
251+
)
252+
for (let i = 0; i < seq.length; i++) {
253+
const bp = roundedStart + i
254+
const codon = seq.slice(i, i + 3)
255+
drawCodon(
256+
ctx,
257+
codon,
258+
seqLeftPx,
259+
i,
260+
theme,
261+
true,
262+
bpPerPx,
263+
bp,
264+
apolloRowHeight,
265+
showFeatureLabels,
266+
showStartCodons,
267+
showStopCodons,
268+
)
269+
}
270+
}
271+
}
174272
},
175273
{ name: 'LinearApolloSixFrameDisplayRenderFeatures' },
176274
),

packages/jbrowse-plugin-apollo/src/util/displayUtils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type CheckResultIdsType } from '@apollo-annotation/mst'
2+
import { type Theme } from '@mui/material'
23
import { makeStyles } from 'tss-react/mui'
34

45
export { default as EditZoomThresholdDialog } from '../components/EditZoomThresholdDialog'
@@ -147,3 +148,30 @@ export function clusterResultByMessage<
147148
)
148149
return clusters
149150
}
151+
152+
export function codonColorCode(
153+
letter: string,
154+
theme: Theme,
155+
highContrast?: boolean,
156+
) {
157+
if (letter === 'M') {
158+
return theme.palette.startCodon
159+
}
160+
if (letter === '*') {
161+
return highContrast ? theme.palette.text.primary : theme.palette.stopCodon
162+
}
163+
return
164+
}
165+
166+
export function colorCode(letter: string, theme: Theme) {
167+
const letterUpper = letter.toUpperCase()
168+
if (
169+
letterUpper === 'A' ||
170+
letterUpper === 'C' ||
171+
letterUpper === 'G' ||
172+
letterUpper === 'T'
173+
) {
174+
return theme.palette.bases[letterUpper].main.toString()
175+
}
176+
return 'lightgray'
177+
}

0 commit comments

Comments
 (0)