Skip to content

Commit

Permalink
Fix rendering alignment arcs on files that need refname renaming and …
Browse files Browse the repository at this point in the history
…add jitter setting (#3416)

* Add exome example to storybook

* Fix refname renaming arc view

* Add to read cloud

* Remove umd example

* Avoid drawing circle with such large radius that it glitches out the
rendering

* Add jitter
  • Loading branch information
cmdcolin committed Dec 17, 2022
1 parent 029e42a commit 96d062b
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 611 deletions.
10 changes: 10 additions & 0 deletions plugins/alignments/src/LinearReadArcsDisplay/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ function configSchemaF(pluginManager: PluginManager) {
defaultValue: 1,
},

/**
* #slot
*/
jitter: {
type: 'number',
description:
'jitters the x position so e.g. if 100 long reads map to same x position, arcs slightly spread out from there',
defaultValue: 2,
},

/**
* #slot
*/
Expand Down
127 changes: 73 additions & 54 deletions plugins/alignments/src/LinearReadArcsDisplay/drawFeats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../shared/color'
import { ChainData } from '../shared/fetchChains'
import { featurizeSA } from '../MismatchParser'
import { Assembly } from '@jbrowse/core/assemblyManager/assembly'

export function hasPairedReads(features: ChainData) {
for (const f of features.chains.values()) {
Expand All @@ -21,6 +22,17 @@ export function hasPairedReads(features: ChainData) {

type LGV = LinearGenomeViewModel

function jitter(n: number) {
return Math.random() * 2 * n - n
}

interface CoreFeat {
strand: number
refName: string
start: number
end: number
}

export default async function drawFeats(
self: {
setLastDrawnOffsetPx: (n: number) => void
Expand All @@ -31,34 +43,48 @@ export default async function drawFeats(
height: number
chainData?: ChainData
lineWidthSetting: number
jitterVal: number
},
ctx: CanvasRenderingContext2D,
) {
const { chainData } = self
const {
chainData,
height,
colorBy,
drawInter,
drawLongRange,
lineWidthSetting,
jitterVal,
} = self
if (!chainData) {
return
}
const displayHeight = self.height
const view = getContainingView(self) as LGV
const { assemblyManager } = getSession(self)
self.setLastDrawnOffsetPx(view.offsetPx)
ctx.lineWidth = self.lineWidthSetting
ctx.lineWidth = lineWidthSetting
const { chains, stats } = chainData
const hasPaired = hasPairedReads(chainData)
const assemblyName = view.assemblyNames[0]
const asm = assemblyManager.get(assemblyName)
const type = self.colorBy?.type || 'insertSizeAndOrientation'
const type = colorBy?.type || 'insertSizeAndOrientation'
if (!asm) {
return
}

function drawLineAtOffset(p: number, c: string) {
// draws a vertical line off to middle of nowhere if the second end not found
ctx.strokeStyle = c
ctx.beginPath()
ctx.moveTo(p, 0)
ctx.lineTo(p, height)
ctx.stroke()
}

function draw(
k1: {
strand: number
refName: string
start: number
end: number
tlen?: number
pair_orientation?: string
},
k2: { strand: number; refName: string; start: number; end: number },
k1: CoreFeat & { tlen?: number; pair_orientation?: string },
k2: CoreFeat,
assembly: Assembly,
longRange?: boolean,
) {
const s1 = k1.strand
Expand All @@ -68,14 +94,16 @@ export default async function drawFeats(

const p1 = f1 ? k1.start : k1.end
const p2 = hasPaired ? (f2 ? k2.start : k2.end) : f2 ? k2.end : k2.start

const r1 = view.bpToPx({ refName: k1.refName, coord: p1 })
const r2 = view.bpToPx({ refName: k2.refName, coord: p2 })
const ra1 = assembly.getCanonicalRefName(k1.refName)
const ra2 = assembly.getCanonicalRefName(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

// bezier (used for non-long-range arcs) requires moveTo before beginPath
// arc (used for long-range) requires moveTo after beginPath (or else a
Expand All @@ -99,9 +127,7 @@ export default async function drawFeats(
} else if (type === 'insertSize') {
ctx.strokeStyle = getInsertSizeColor(k1, k2, stats) || 'grey'
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${
Math.log10(Math.abs(p1 - p2)) * 10
},50%,50%)`
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
} else {
if (type === 'orientation' || type === 'insertSizeAndOrientation') {
Expand All @@ -113,53 +139,55 @@ export default async function drawFeats(
ctx.strokeStyle = 'grey'
}
} else if (type === 'gradient') {
ctx.strokeStyle = `hsl(${
Math.log10(Math.abs(p1 - p2)) * 10
},50%,50%)`
ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
}
}
}

const destX = p + radius * 2
const destY = Math.min(displayHeight, absrad)
const destY = Math.min(height + jitter(jitterVal), absrad)
if (longRange) {
ctx.arc(p + radius, 0, absrad, 0, Math.PI)
// avoid drawing gigantic circles that glitch out the rendering,
// instead draw vertical lines
if (absrad > 100_000) {
drawLineAtOffset(p + jitter(jitterVal), 'red')
drawLineAtOffset(p2 + jitter(jitterVal), 'red')
} else {
ctx.arc(p + radius + jitter(jitterVal), 0, absrad, 0, Math.PI)
ctx.stroke()
}
} else {
ctx.bezierCurveTo(p, destY, destX, destY, destX, 0)
ctx.bezierCurveTo(
p + jitter(jitterVal),
destY,
destX,
destY,
destX + jitter(jitterVal),
0,
)
ctx.stroke()
}
ctx.stroke()
} else if (r1 && self.drawInter) {
// draws a vertical line off to middle of nowhere if the second end not found
const p = r1.offsetPx - view.offsetPx
ctx.strokeStyle = 'purple'
ctx.beginPath()
ctx.moveTo(p, 0)
ctx.lineTo(p, displayHeight)
ctx.stroke()
} else if (r1 && drawInter) {
drawLineAtOffset(r1.offsetPx - view.offsetPx, 'purple')
}
}

for (let i = 0; i < chains.length; i++) {
let chain = chains[i]

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

// special case where we look at RPOS/RNEXT
if (hasPaired) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const refName = asm?.getCanonicalRefName(f.next_ref!) || f.next_ref!
const refName = f.next_ref!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const coord = f.next_pos!
draw(
f,
{
refName,
start: coord,
end: coord,
strand: f.strand,
},
{ refName, start: coord, end: coord, strand: f.strand },
asm,
true,
)
}
Expand All @@ -171,16 +199,7 @@ export default async function drawFeats(
for (let i = 0; i < features.length - 1; i++) {
const f = features[i]
const v1 = features[i + 1]
draw(
f,
{
refName: asm?.getCanonicalRefName(v1.refName) || v1.refName,
start: v1.start,
end: v1.end,
strand: v1.strand,
},
true,
)
draw(f, v1, asm, true)
}
}
} else {
Expand All @@ -192,7 +211,7 @@ export default async function drawFeats(
chain = chain.filter(f => !(f.flags & 2048))
}
for (let i = 0; i < chain.length - 1; i++) {
draw(chain[i], chain[i + 1], false)
draw(chain[i], chain[i + 1], asm, false)
}
}
}
Expand Down
45 changes: 45 additions & 0 deletions plugins/alignments/src/LinearReadArcsDisplay/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
*/
lineWidth: types.maybe(types.number),

/**
* #property
*/
jitter: types.maybe(types.number),

/**
* #property
*/
Expand Down Expand Up @@ -175,6 +180,15 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
setLineWidth(n: number) {
self.lineWidth = n
},

/**
* #action
* jitter val, helpful to jitter the x direction so you see better evidence when e.g. 100
* long reads map to same x position
*/
setJitter(n: number) {
self.jitter = n
},
}))

.views(self => {
Expand All @@ -190,6 +204,13 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
get lineWidthSetting() {
return self.lineWidth ?? getConf(self, 'lineWidth')
},

/**
* #getter
*/
get jitterVal(): number {
return self.jitter ?? getConf(self, 'jitter')
},
/**
* #getter
*/
Expand Down Expand Up @@ -245,6 +266,30 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
},
],
},
{
label: 'Jitter x-positions',
subMenu: [
{
type: 'checkbox',
checked: this.jitterVal === 0,
label: 'None',
onClick: () => self.setJitter(0),
},
{
type: 'checkbox',
checked: this.jitterVal === 2,
label: 'Small',
onClick: () => self.setJitter(2),
},
{
type: 'checkbox',
checked: this.jitterVal === 10,
label: 'Large',
onClick: () => self.setJitter(10),
},
],
},

{
label: 'Draw inter-region vertical lines',
type: 'checkbox',
Expand Down
54 changes: 21 additions & 33 deletions plugins/alignments/src/LinearReadCloudDisplay/drawFeats.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getConf } from '@jbrowse/core/configuration'
import { getContainingView } from '@jbrowse/core/util'
import { getContainingView, getSession } from '@jbrowse/core/util'

import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
// locals
Expand Down Expand Up @@ -60,9 +60,15 @@ export default async function drawFeats(
if (!chainData) {
return
}
const { assemblyManager } = getSession(self)
const featureHeight = getConf(self, 'featureHeight')
const displayHeight = self.height
const view = getContainingView(self) as LGV
const assemblyName = view.assemblyNames[0]
const asm = assemblyManager.get(assemblyName)
if (!asm) {
return
}

self.setLastDrawnOffsetPx(view.offsetPx)

Expand All @@ -76,22 +82,13 @@ export default async function drawFeats(
if (chain[0].flags & 1 && chain.length > 1) {
const v0 = chain[0]
const v1 = chain[1]
const r1s = view.bpToPx({
refName: v0.refName,
coord: v0.start,
})
const r1e = view.bpToPx({
refName: v0.refName,
coord: v0.end,
})
const r2s = view.bpToPx({
refName: v1.refName,
coord: v1.start,
})
const r2e = view.bpToPx({
refName: v1.refName,
coord: v1.end,
})
const ra1 = asm.getCanonicalRefName(v0.refName)
const ra2 = asm.getCanonicalRefName(v1.refName)
const r1s = view.bpToPx({ refName: ra1, coord: v0.start })
const r1e = view.bpToPx({ refName: ra1, coord: v0.end })
const r2s = view.bpToPx({ refName: ra2, coord: v1.start })
const r2e = view.bpToPx({ refName: ra2, coord: v1.end })

let distance = 0

if (
Expand Down Expand Up @@ -121,22 +118,13 @@ export default async function drawFeats(
for (let i = 1; i < chain.length; i++) {
const v0 = chain[i - 1]
const v1 = chain[i]
const r1s = view.bpToPx({
refName: v0.refName,
coord: v0.start,
})
const r1e = view.bpToPx({
refName: v0.refName,
coord: v0.end,
})
const r2s = view.bpToPx({
refName: v1.refName,
coord: v1.start,
})
const r2e = view.bpToPx({
refName: v1.refName,
coord: v1.end,
})
const ra1 = asm.getCanonicalRefName(v0.refName)
const ra2 = asm.getCanonicalRefName(v1.refName)
const r1s = view.bpToPx({ refName: ra1, coord: v0.start })
const r1e = view.bpToPx({ refName: ra1, coord: v0.end })
const r2s = view.bpToPx({ refName: ra2, coord: v1.start })
const r2e = view.bpToPx({ refName: ra2, coord: v1.end })

let distance = 0

if (
Expand Down
Loading

0 comments on commit 96d062b

Please sign in to comment.