Skip to content

Commit

Permalink
Allow X/Y assemblies of dotplot or top/bottom selection of synteny vi…
Browse files Browse the repository at this point in the history
…ews to be either query or target (#3178)

* Handle reversed cigar orientation

* Fix for linearsyntenyview

* Add test

* Avoid drawing very large offscreen rect background for dotplot

* Unify logic
  • Loading branch information
cmdcolin authored Sep 12, 2022
1 parent a7e7da9 commit c8c79c4
Show file tree
Hide file tree
Showing 9 changed files with 1,718 additions and 33 deletions.
14 changes: 10 additions & 4 deletions plugins/comparative-adapters/src/PAFAdapter/PAFAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,18 @@ export default class PAFAdapter extends BaseFeatureDataAdapter {
end,
refName,
strand,
assemblyName,
revCigar: true,
// this is a special property of how to interpret CIGAR on PAF,
// intrinsic to the data format. the CIGAR is read backwards
// for features aligning to the negative strand of the target,
// which is actually different than how it works in e.g.
// BAM/SAM (which is visible during alignments track read vs ref)
assemblyName,

// depending on whether the query or target is queried, the "rev" flag
flipInsDel: index === 0,
syntenyId: i,
...(numMatches && blockLen
? { identity: numMatches / blockLen }
: {}),
identity: (numMatches || 0) / (blockLen || 1),
mate: { start: mateStart, end: mateEnd, refName: mateName },
...extra,
}),
Expand Down
15 changes: 13 additions & 2 deletions plugins/dotplot-view/src/DotplotRenderer/DotplotRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,27 +225,38 @@ export default class DotplotRenderer extends ComparativeRenderer {
let currX = b1
let currY = e1
const cigar = feature.get('cg') || feature.get('CIGAR')
const flipInsDel = feature.get('flipInsDel')

if (drawCigar && cigar) {
const cigarOps = parseCigar(cigar)

ctx.beginPath()
ctx.moveTo(currX, height - currY)

for (let i = 0; i < cigarOps.length; i += 2) {
const val = +cigarOps[i]
const op = cigarOps[i + 1]
if (op === 'M' || op === '=' || op === 'X') {
currX += (val / hBpPerPx) * strand
currY += val / vBpPerPx
} else if (op === 'D' || op === 'N') {
currX += (val / hBpPerPx) * strand
if (flipInsDel) {
currY += val / vBpPerPx
} else {
currX += (val / hBpPerPx) * strand
}
} else if (op === 'I') {
currY += val / vBpPerPx
if (flipInsDel) {
currX += (val / hBpPerPx) * strand
} else {
currY += val / vBpPerPx
}
}
currX = clampWithWarnX(currX, b1, b2, feature)
currY = clampWithWarnY(currY, e1, e2, feature)
ctx.lineTo(currX, height - currY)
}

ctx.stroke()
} else {
ctx.beginPath()
Expand Down
14 changes: 7 additions & 7 deletions plugins/dotplot-view/src/DotplotView/components/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ function Grid({
const hbottom = hblocks[0]?.offsetPx - hview.offsetPx
const vbottom = vblocks[0]?.offsetPx - vview.offsetPx

// Uses math.max/math.min avoid making very large SVG rect offscreen element,
// which can sometimes fail to draw
const rx = Math.max(hbottom, 0)
const ry = Math.max(viewHeight - vtop, 0)
const w = Math.min(htop - hbottom, viewWidth)
const h = Math.min(vtop - vbottom, viewHeight)
return (
<svg
style={{ background: 'rgba(0,0,0,0.12)' }}
width={viewWidth}
height={viewHeight}
>
<rect
x={hbottom}
y={viewHeight - vtop}
width={htop - hbottom}
height={vtop - vbottom}
fill="#fff"
/>
<rect x={rx} y={ry} width={w} height={h} fill="#fff" />
<g>
{hblocks.map(region => {
const x = region.offsetPx - hview.offsetPx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,9 @@ export default class LinearSyntenyRenderer extends ComparativeServerSideRenderer
width: number
view: { views: Base1DViewModel[] }
}) {
const {
width,
view: { views },
} = renderProps
const realizedViews = views.map(snap => {
const view = Base1DView.create(snap)
view.setVolatileWidth(width)
return view
})
const { width, view } = renderProps
const realizedViews = view.views.map(snap => Base1DView.create(snap))
realizedViews.forEach(v => v.setVolatileWidth(width))
const features = await Promise.all(
realizedViews.map(view =>
this.getFeatures({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,7 @@ function drawMatchSimple({
const x22 = px22 - offsets[l2]

const y1 = interstitialYPos(l1 < l2, height)

const y2 = interstitialYPos(l2 < l1, height)

const mid = (y2 - y1) / 2

// drawing a line if the results are thin: drawing a line results in much
Expand Down Expand Up @@ -406,6 +404,7 @@ function LinearSyntenyRendering({

// we have to read the CIGAR backwards when looking at negative strand features
const f1flipped = f1.get('revCigar') && f1.get('strand') === -1
const flipInsDel = f1.get('flipInsDel')

// flip the direction of the CIGAR drawing in horizontally flipped
// modes
Expand Down Expand Up @@ -440,15 +439,22 @@ function LinearSyntenyRendering({

const d1 = len / viewSnaps[0].bpPerPx
const d2 = len / viewSnaps[1].bpPerPx

if (op === 'M' || op === '=' || op === 'X') {
cx1 += d1 * rev1
cx2 += d2 * rev2
} else if (op === 'D') {
cx1 += d1 * rev1
} else if (op === 'N') {
cx1 += d1 * rev1
} else if (op === 'D' || op === 'N') {
if (flipInsDel) {
cx2 += d2 * rev2
} else {
cx1 += d1 * rev1
}
} else if (op === 'I') {
cx2 += d2 * rev2
if (flipInsDel) {
cx1 += d1 * rev1
} else {
cx2 += d2 * rev2
}
}

// check that we are even drawing in view here, e.g. that all
Expand Down
16 changes: 14 additions & 2 deletions products/jbrowse-web/src/tests/Dotplot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TextEncoder, TextDecoder } from 'web-encoding'
import path from 'path'
import dotplotConfig from '../../test_data/config_dotplot.json'
import dotplotSession from './dotplot_inverted_test.json'
import dotplotSessionFlipAxes from './dotplot_inverted_flipaxes.json'
import {
setup,
generateReadBuffer,
Expand Down Expand Up @@ -72,5 +73,16 @@ test('inverted dotplot', async () => {
...dotplotConfig,
defaultSession: dotplotSession.session,
})
expectCanvasMatch(await findByTestId('prerendered_canvas_done', {}, delay))
})
expectCanvasMatch(await findByTestId('prerendered_canvas_done', {}, delay), 0)
}, 30000)

test('inverted dotplot flip axes', async () => {
const { findByTestId } = createView({
...dotplotConfig,
defaultSession: dotplotSessionFlipAxes.session,
})
expectCanvasMatch(await findByTestId('prerendered_canvas_done', {}, delay), 0)
}, 30000)

// session with dotplots and linear synteny views with both orientations tested
// http://localhost:3000/?config=test_data%2Fconfig_dotplot.json&session=share-GGmzKoxYlo&password=JhsC4
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c8c79c4

Please sign in to comment.