From 31f7313405ea3aac7f38a9b4ddaf1d77cbd31aec Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 13 Feb 2021 17:15:08 -0700 Subject: [PATCH] Calculate clipPos forward strand even on reverse strand reads to get sequence track right on both top and bottom --- packages/core/util/index.ts | 50 ++++++++++++++++++ plugins/linear-comparative-view/src/index.tsx | 36 ++++++++----- .../components/DivSequenceRendering.tsx | 52 +------------------ 3 files changed, 73 insertions(+), 65 deletions(-) diff --git a/packages/core/util/index.ts b/packages/core/util/index.ts index fd05a92da14..57ce86c4b6f 100644 --- a/packages/core/util/index.ts +++ b/packages/core/util/index.ts @@ -811,3 +811,53 @@ export function stringify({ export const isElectron = typeof window !== 'undefined' && Boolean(window.electron) + +export function revcom(seqString: string) { + return complement(seqString).split('').reverse().join('') +} + +export const complement = (() => { + const complementRegex = /[ACGT]/gi + + // from bioperl: tr/acgtrymkswhbvdnxACGTRYMKSWHBVDNX/tgcayrkmswdvbhnxTGCAYRKMSWDVBHNX/ + // generated with: + // perl -MJSON -E '@l = split "","acgtrymkswhbvdnxACGTRYMKSWHBVDNX"; print to_json({ map { my $in = $_; tr/acgtrymkswhbvdnxACGTRYMKSWHBVDNX/tgcayrkmswdvbhnxTGCAYRKMSWDVBHNX/; $in => $_ } @l})' + const complementTable = { + S: 'S', + w: 'w', + T: 'A', + r: 'y', + a: 't', + N: 'N', + K: 'M', + x: 'x', + d: 'h', + Y: 'R', + V: 'B', + y: 'r', + M: 'K', + h: 'd', + k: 'm', + C: 'G', + g: 'c', + t: 'a', + A: 'T', + n: 'n', + W: 'W', + X: 'X', + m: 'k', + v: 'b', + B: 'V', + s: 's', + H: 'D', + c: 'g', + D: 'H', + b: 'v', + R: 'Y', + G: 'C', + } as { [key: string]: string } + + return (seqString: string) => { + return seqString.replace(complementRegex, m => complementTable[m] || '') + } +})() diff --git a/plugins/linear-comparative-view/src/index.tsx b/plugins/linear-comparative-view/src/index.tsx index 0b9c2854c16..e98f7b82fbe 100644 --- a/plugins/linear-comparative-view/src/index.tsx +++ b/plugins/linear-comparative-view/src/index.tsx @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any,no-bitwise */ import React, { useState } from 'react' import { makeStyles } from '@material-ui/core/styles' import Button from '@material-ui/core/Button' @@ -121,6 +121,7 @@ interface ReducedFeature { start: number clipPos: number end: number + strand: number seqLength: number syntenyId?: number uniqueId: string @@ -164,8 +165,8 @@ function WindowSizeDlg(props: { try { const session = getSession(track) const view = getContainingView(track) - const clipPos = feature.get('clipPos') const cigar = feature.get('CIGAR') + const clipPos = getClip(cigar, 1) const flags = feature.get('flags') const SA: string = (feature.get('tags') ? feature.get('tags').SA : feature.get('SA')) || '' @@ -190,7 +191,7 @@ function WindowSizeDlg(props: { const saLength = getLength(saCigar) const saLengthSansClipping = getLengthSansClipping(saCigar) const saStrandNormalized = saStrand === '-' ? -1 : 1 - const saClipPos = getClip(saCigar, saStrandNormalized) + const saClipPos = getClip(saCigar, 1) // saStrandNormalized) const saRealStart = +saStart - 1 return { refName: saRef, @@ -211,6 +212,7 @@ function WindowSizeDlg(props: { }) const feat = feature.toJSON() + feat.clipPos = clipPos feat.mate = { refName: readName, @@ -222,7 +224,6 @@ function WindowSizeDlg(props: { // which is the primary alignments. otherwise it is the primary alignment just use // seq.length if primary alignment const totalLength = - // eslint-disable-next-line no-bitwise flags & 2048 ? getLength(supplementaryAlignments[0].CIGAR) : getLength(cigar) @@ -237,6 +238,8 @@ function WindowSizeDlg(props: { }) features.sort((a, b) => a.clipPos - b.clipPos) + const featSeq = feature.get('seq') + // the config feature store includes synthetic mate features // mapped to the read assembly const configFeatureStore = features.concat( @@ -244,13 +247,25 @@ function WindowSizeDlg(props: { features.map(f => f.mate), ) + const expand = 2 * windowSize const refLength = features.reduce( - (a, f) => a + f.end - f.start + 2 * windowSize, + (a, f) => a + f.end - f.start + expand, 0, ) const seqTrackId = `${readName}_${Date.now()}` const sequenceTrackConf = getConf(assembly, 'sequence') + const lgvRegions = features + .map(f => { + return { + ...f, + start: f.start - windowSize, + end: f.end + windowSize, + refName: f.refName, + assemblyName: trackAssembly, + } + }) + .sort((a, b) => a.clipPos - b.clipPos) session.addView('LinearSyntenyView', { type: 'LinearSyntenyView', @@ -260,14 +275,7 @@ function WindowSizeDlg(props: { hideHeader: true, offsetPx: 0, bpPerPx: refLength / view.width, - displayedRegions: features.map(f => { - return { - start: f.start - windowSize, - end: f.end + windowSize, - refName: f.refName, - assemblyName: trackAssembly, - } - }), + displayedRegions: lgvRegions, tracks: [ { id: `${Math.random()}`, @@ -345,7 +353,7 @@ function WindowSizeDlg(props: { { start: 0, end: totalLength, - seq: feat.seq, + seq: featSeq, refName: readName, uniqueId: `${Math.random()}`, id: `${Math.random()}`, diff --git a/plugins/sequence/src/DivSequenceRenderer/components/DivSequenceRendering.tsx b/plugins/sequence/src/DivSequenceRenderer/components/DivSequenceRendering.tsx index 73d1abf4f6e..13670108595 100644 --- a/plugins/sequence/src/DivSequenceRenderer/components/DivSequenceRendering.tsx +++ b/plugins/sequence/src/DivSequenceRenderer/components/DivSequenceRendering.tsx @@ -6,7 +6,7 @@ import { Region } from '@jbrowse/core/util/types' import { createJBrowseTheme } from '@jbrowse/core/ui' import { observer } from 'mobx-react' import React from 'react' -import { bpSpanPx } from '@jbrowse/core/util' +import { bpSpanPx, revcom, complement } from '@jbrowse/core/util' interface MyProps { features: Map @@ -20,56 +20,6 @@ interface MyProps { showTranslation: boolean } -function revcom(seqString: string) { - return complement(seqString).split('').reverse().join('') -} - -const complement = (() => { - const complementRegex = /[ACGT]/gi - - // from bioperl: tr/acgtrymkswhbvdnxACGTRYMKSWHBVDNX/tgcayrkmswdvbhnxTGCAYRKMSWDVBHNX/ - // generated with: - // perl -MJSON -E '@l = split "","acgtrymkswhbvdnxACGTRYMKSWHBVDNX"; print to_json({ map { my $in = $_; tr/acgtrymkswhbvdnxACGTRYMKSWHBVDNX/tgcayrkmswdvbhnxTGCAYRKMSWDVBHNX/; $in => $_ } @l})' - const complementTable = { - S: 'S', - w: 'w', - T: 'A', - r: 'y', - a: 't', - N: 'N', - K: 'M', - x: 'x', - d: 'h', - Y: 'R', - V: 'B', - y: 'r', - M: 'K', - h: 'd', - k: 'm', - C: 'G', - g: 'c', - t: 'a', - A: 'T', - n: 'n', - W: 'W', - X: 'X', - m: 'k', - v: 'b', - B: 'V', - s: 's', - H: 'D', - c: 'g', - D: 'H', - b: 'v', - R: 'Y', - G: 'C', - } as { [key: string]: string } - - return (seqString: string) => { - return seqString.replace(complementRegex, m => complementTable[m] || '') - } -})() - const defaultStarts = ['ATG'] const defaultStops = ['TAA', 'TAG', 'TGA'] const defaultCodonTable = {