Skip to content

Commit 14ba864

Browse files
Fix sequence track when displaying multiple regions (GMOD#724)
* Fix variable name * Don't draw frame colors outside displayed regions * Fix sequence not displaying sometimes * Use contentBlocks iterator * Factor track drawing into separate files
1 parent 7555bd7 commit 14ba864

File tree

4 files changed

+480
-413
lines changed

4 files changed

+480
-413
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)