Skip to content

Commit

Permalink
idle callback to update arcs
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed May 11, 2023
1 parent 7a95722 commit 7c0ea07
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 140 deletions.
254 changes: 133 additions & 121 deletions plugins/alignments/src/LinearReadArcsDisplay/drawFeats.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getContainingView, getSession } from '@jbrowse/core/util'
import { getContainingView, getSession, rIC } from '@jbrowse/core/util'
import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'

// locals
Expand Down Expand Up @@ -51,10 +51,11 @@ function drawLineAtOffset(
export default function drawFeats(
self: LinearReadArcsDisplayModel,
ctx: CanvasRenderingContext2D,
width: number,
height: number,
) {
const {
chainData,
height,
colorBy,
drawInter,
drawLongRange,
Expand All @@ -73,82 +74,98 @@ export default function drawFeats(
if (!asm) {
return
}
ctx.lineWidth = lineWidthSetting
function draw(
k1: CoreFeat & { tlen?: number; pair_orientation?: string },
k2: CoreFeat,
assembly: Assembly,
longRange?: boolean,
) {
const s1 = k1.strand
const s2 = k2.strand
const f1 = s1 === -1
const f2 = s2 === -1
self.setDrawing(true)
rIC(() => {
ctx.clearRect(0, 0, width, height * 2)
ctx.resetTransform()
ctx.scale(2, 2)
ctx.lineWidth = lineWidthSetting
function draw(
k1: CoreFeat & { tlen?: number; pair_orientation?: string },
k2: CoreFeat,
assembly: Assembly,
longRange?: boolean,
) {
const s1 = k1.strand
const s2 = k2.strand
const f1 = s1 === -1
const f2 = s2 === -1

const p1 = f1 ? k1.start : k1.end
const p2 = hasPaired ? (f2 ? k2.start : k2.end) : f2 ? k2.end : k2.start
const ra1 = assembly.getCanonicalRefName(k1.refName) || k1.refName
const ra2 = assembly.getCanonicalRefName(k2.refName) || k2.refName
const r1 = view.bpToPx({ refName: ra1, coord: p1 })
const r2 = view.bpToPx({ refName: ra2, coord: p2 })
const p1 = f1 ? k1.start : k1.end
const p2 = hasPaired ? (f2 ? k2.start : k2.end) : f2 ? k2.end : k2.start
const ra1 = assembly.getCanonicalRefName(k1.refName) || k1.refName
const ra2 = assembly.getCanonicalRefName(k2.refName) || k2.refName
const r1 = view.bpToPx({ refName: ra1, coord: p1 })
const r2 = view.bpToPx({ refName: ra2, coord: p2 })

if (r1 && r2) {
const radius = (r2.offsetPx - r1.offsetPx) / 2
const absrad = Math.abs(radius)
const p = r1.offsetPx - view.offsetPx
const p2 = r2.offsetPx - view.offsetPx
const drawArcInsteadOfBezier = absrad > 10_000
if (r1 && r2) {
const radius = (r2.offsetPx - r1.offsetPx) / 2
const absrad = Math.abs(radius)
const p = r1.offsetPx - view.offsetPx
const p2 = r2.offsetPx - view.offsetPx
const drawArcInsteadOfBezier = absrad > 10_000

// bezier (used for non-long-range arcs) requires moveTo before beginPath
// arc (used for long-range) requires moveTo after beginPath (or else a
// unwanted line at y=0 is rendered along with the arc)
if (longRange && drawArcInsteadOfBezier) {
ctx.moveTo(p, 0)
ctx.beginPath()
} else {
ctx.beginPath()
ctx.moveTo(p, 0)
}
// bezier (used for non-long-range arcs) requires moveTo before beginPath
// arc (used for long-range) requires moveTo after beginPath (or else a
// unwanted line at y=0 is rendered along with the arc)
if (longRange && drawArcInsteadOfBezier) {
ctx.moveTo(p, 0)
ctx.beginPath()
} else {
ctx.beginPath()
ctx.moveTo(p, 0)
}

if (longRange && drawArcInsteadOfBezier) {
ctx.strokeStyle = 'red'
} else {
if (hasPaired) {
if (type === 'insertSizeAndOrientation') {
ctx.strokeStyle = getInsertSizeAndOrientationColor(k1, k2, stats)
} else if (type === 'orientation') {
ctx.strokeStyle = getOrientationColor(k1)
} else if (type === 'insertSize') {
ctx.strokeStyle = getInsertSizeColor(k1, k2, stats) || 'grey'
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
if (longRange && drawArcInsteadOfBezier) {
ctx.strokeStyle = 'red'
} else {
if (type === 'orientation' || type === 'insertSizeAndOrientation') {
if (s1 === -1 && s2 === 1) {
ctx.strokeStyle = 'navy'
} else if (s1 === 1 && s2 === -1) {
ctx.strokeStyle = 'green'
} else {
ctx.strokeStyle = 'grey'
if (hasPaired) {
if (type === 'insertSizeAndOrientation') {
ctx.strokeStyle = getInsertSizeAndOrientationColor(k1, k2, stats)
} else if (type === 'orientation') {
ctx.strokeStyle = getOrientationColor(k1)
} else if (type === 'insertSize') {
ctx.strokeStyle = getInsertSizeColor(k1, k2, stats) || 'grey'
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
} else {
if (type === 'orientation' || type === 'insertSizeAndOrientation') {
if (s1 === -1 && s2 === 1) {
ctx.strokeStyle = 'navy'
} else if (s1 === 1 && s2 === -1) {
ctx.strokeStyle = 'green'
} else {
ctx.strokeStyle = 'grey'
}
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
}
}

const destX = p + radius * 2
const destY = Math.min(height + jitter(jitterVal), absrad)
if (longRange) {
// avoid drawing gigantic circles that glitch out the rendering,
// instead draw vertical lines
if (absrad > 100_000) {
drawLineAtOffset(ctx, p + jitter(jitterVal), height, 'red')
drawLineAtOffset(ctx, p2 + jitter(jitterVal), height, 'red')
} else if (drawArcInsteadOfBezier) {
ctx.arc(p + radius + jitter(jitterVal), 0, absrad, 0, Math.PI)
ctx.stroke()
const destX = p + radius * 2
const destY = Math.min(height + jitter(jitterVal), absrad)
if (longRange) {
// avoid drawing gigantic circles that glitch out the rendering,
// instead draw vertical lines
if (absrad > 100_000) {
drawLineAtOffset(ctx, p + jitter(jitterVal), height, 'red')
drawLineAtOffset(ctx, p2 + jitter(jitterVal), height, 'red')
} else if (drawArcInsteadOfBezier) {
ctx.arc(p + radius + jitter(jitterVal), 0, absrad, 0, Math.PI)
ctx.stroke()
} else {
ctx.bezierCurveTo(
p + jitter(jitterVal),
destY,
destX,
destY,
destX + jitter(jitterVal),
0,
)
ctx.stroke()
}
} else {
ctx.bezierCurveTo(
p + jitter(jitterVal),
Expand All @@ -160,64 +177,59 @@ export default function drawFeats(
)
ctx.stroke()
}
} else {
ctx.bezierCurveTo(
p + jitter(jitterVal),
destY,
destX,
destY,
destX + jitter(jitterVal),
0,
)
ctx.stroke()
} else if (r1 && drawInter) {
drawLineAtOffset(ctx, r1.offsetPx - view.offsetPx, height, 'purple')
}
} else if (r1 && drawInter) {
drawLineAtOffset(ctx, r1.offsetPx - view.offsetPx, height, 'purple')
}
}

for (const chain of chains) {
if (chain.length === 1 && drawLongRange) {
// singleton feature
const f = chain[0]
for (const chain of chains) {
if (chain.length === 1 && drawLongRange) {
// singleton feature
const f = chain[0]

// special case where we look at RPOS/RNEXT
if (hasPaired) {
const coord = f.next_pos || 0
draw(
f,
{
refName: f.next_ref || '',
start: coord,
end: coord,
strand: f.strand,
},
asm,
true,
)
}
// special case where we look at RPOS/RNEXT
if (hasPaired) {
const coord = f.next_pos || 0
draw(
f,
{
refName: f.next_ref || '',
start: coord,
end: coord,
strand: f.strand,
},
asm,
true,
)
}

// special case where we look at SA
else {
const suppAlns = featurizeSA(f.SA, f.id, f.strand, f.name)
const features = [f, ...suppAlns].sort((a, b) => a.clipPos - b.clipPos)
for (let i = 0; i < features.length - 1; i++) {
const f = features[i]
const v1 = features[i + 1]
draw(f, v1, asm, true)
// special case where we look at SA
else {
const suppAlns = featurizeSA(f.SA, f.id, f.strand, f.name)
const features = [f, ...suppAlns].sort(
(a, b) => a.clipPos - b.clipPos,
)
for (let i = 0; i < features.length - 1; i++) {
const f = features[i]
const v1 = features[i + 1]
draw(f, v1, asm, true)
}
}
} else {
const res = hasPaired
? chain.filter(f => !(f.flags & 2048))
: chain
.sort((a, b) => a.clipPos - b.clipPos)
.filter(f => !(f.flags & 256))
for (let i = 0; i < res.length - 1; i++) {
draw(res[i], res[i + 1], asm, false)
}
}
} else {
const res = hasPaired
? chain.filter(f => !(f.flags & 2048))
: chain
.sort((a, b) => a.clipPos - b.clipPos)
.filter(f => !(f.flags & 256))
for (let i = 0; i < res.length - 1; i++) {
draw(res[i], res[i + 1], asm, false)
}
}
}
self.setDrawn(true)
self.setLastDrawnOffsetPx(view.offsetPx)
self.setLastDrawnOffsetPx(view.offsetPx)
rIC(() => {
self.setDrawn(true)
self.setDrawing(false)
})
})
}
13 changes: 9 additions & 4 deletions plugins/alignments/src/LinearReadArcsDisplay/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
)
.volatile(() => ({
loading: false,
drawing: false,
drawn: false,
chainData: undefined as ChainData | undefined,
lastDrawnOffsetPx: 0,
Expand All @@ -111,6 +112,12 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
setLoading(f: boolean) {
self.loading = f
},
/**
* #action
*/
setDrawing(f: boolean) {
self.drawing = f
},
/**
* #action
*/
Expand Down Expand Up @@ -342,10 +349,8 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
if (!ctx) {
return
}
ctx.clearRect(0, 0, canvas.width, self.height * 2)
ctx.resetTransform()
ctx.scale(2, 2)
drawFeats(self, ctx)

drawFeats(self, ctx, canvas.width, self.height)
},
{ delay: 1000 },
)
Expand Down
14 changes: 10 additions & 4 deletions plugins/alignments/src/LinearReadCloudDisplay/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
)
.volatile(() => ({
loading: false,
drawing: false,
drawn: false,
chainData: undefined as ChainData | undefined,
ref: null as HTMLCanvasElement | null,
Expand All @@ -84,6 +85,12 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
setLastDrawnOffsetPx(n: number) {
self.lastDrawnOffsetPx = n
},
/**
* #action
*/
setDrawing(f: boolean) {
self.drawing = f
},
/**
* #action
*/
Expand Down Expand Up @@ -214,11 +221,10 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
if (!ctx) {
return
}
ctx.clearRect(0, 0, canvas.width, self.height * 2)
ctx.resetTransform()
ctx.scale(2, 2)

drawFeats(self, ctx)
const width = canvas.width
const height = self.height
drawFeats(self, ctx, width, height)
})
},
}))
Expand Down
Loading

0 comments on commit 7c0ea07

Please sign in to comment.