Skip to content

Commit

Permalink
PileupRenderer refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed May 30, 2023
1 parent f3e2223 commit ab283f7
Show file tree
Hide file tree
Showing 29 changed files with 1,156 additions and 1,093 deletions.
1,107 changes: 19 additions & 1,088 deletions plugins/alignments/src/PileupRenderer/PileupRenderer.ts

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions plugins/alignments/src/PileupRenderer/colorBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import { Feature } from '@jbrowse/core/util'
import { fillColor } from '../shared/color'
import { orientationTypes } from '../util'

export function colorByInsertSize(feature: Feature) {
return feature.get('is_paired') &&
feature.get('refName') !== feature.get('next_ref')
? '#555'
: `hsl(${Math.abs(feature.get('template_length')) / 10},50%,50%)`
}

export function colorByMappingQuality(feature: Feature) {
return `hsl(${feature.get('mq')},50%,50%)`
}

function getOrientation(feature: Feature, config: AnyConfigurationModel) {
const orientationType = readConfObject(config, 'orientationType') as
| 'fr'
| 'ff'
| 'rf'
const type = orientationTypes[orientationType]
const orientation = type[feature.get('pair_orientation') as string]
return {
LR: 'color_pair_lr' as const,
RR: 'color_pair_rr' as const,
RL: 'color_pair_rl' as const,
LL: 'color_pair_ll' as const,
}[orientation]
}

export function colorByStrand(feature: Feature) {
return feature.get('strand') === -1 ? '#8F8FD8' : '#EC8B8B'
}

export function colorByOrientation(
feature: Feature,
config: AnyConfigurationModel,
) {
return fillColor[getOrientation(feature, config) || 'color_nostrand']
}
function getStranded(feature: Feature) {
const flags = feature.get('flags')
const strand = feature.get('strand')
// is paired
if (flags & 1) {
const revflag = flags & 64
const flipper = revflag ? -1 : 1

// proper pairing
if (flags & 2) {
return strand * flipper === 1 ? 'color_rev_strand' : 'color_fwd_strand'
} else if (feature.get('multi_segment_next_segment_unmapped')) {
return strand * flipper === 1
? 'color_rev_missing_mate'
: 'color_fwd_missing_mate'
} else if (feature.get('refName') === feature.get('next_refName')) {
return strand * flipper === 1
? 'color_rev_strand_not_proper'
: 'color_fwd_strand_not_proper'
} else {
// should only leave aberrant chr
return strand === 1 ? 'color_fwd_diff_chr' : 'color_rev_diff_chr'
}
}
return strand === 1 ? 'color_fwd_strand' : 'color_rev_strand'
}

export function colorByStrandedRnaSeq(feature: Feature) {
return fillColor[getStranded(feature)]
}
87 changes: 87 additions & 0 deletions plugins/alignments/src/PileupRenderer/getAlignmentShapeColor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
AnyConfigurationModel,
readConfObject,
} from '@jbrowse/core/configuration'
import { Feature } from '@jbrowse/core/util'
import { fillColor } from '../shared/color'
import {
colorByInsertSize,
colorByMappingQuality,
colorByOrientation,
colorByStrand,
colorByStrandedRnaSeq,
} from './colorBy'

export function getAlignmentShapeColor({
colorType,
tag,
feature,
config,
defaultColor,
colorTagMap,
}: {
colorType: string
tag: string
feature: Feature
defaultColor: boolean
config: AnyConfigurationModel
colorTagMap: Record<string, string>
}) {
// first pass for simple color changes that change the color of the
// alignment
switch (colorType) {
case 'insertSize':
return colorByInsertSize(feature)
case 'strand':
return colorByStrand(feature)
case 'mappingQuality':
return colorByMappingQuality(feature)
case 'pairOrientation':
return colorByOrientation(feature, config)
case 'stranded':
return colorByStrandedRnaSeq(feature)
case 'xs':
case 'tag': {
const tags = feature.get('tags')
const val = tags ? tags[tag] : feature.get(tag)

if (tag === 'XS' || tag === 'TS') {
return fillColor[
{
'-': 'color_rev_strand' as const,
'+': 'color_fwd_strand' as const,
}[val as '-' | '+'] || 'color_nostrand'
]
} else if (tag === 'ts') {
return fillColor[
{
'-':
feature.get('strand') === -1
? ('color_fwd_strand' as const)
: ('color_rev_strand' as const),
'+':
feature.get('strand') === -1
? ('color_rev_strand' as const)
: ('color_fwd_strand' as const),
}[val as '-' | '+'] || 'color_nostrand'
]
} else {
return colorTagMap[val] || fillColor['color_nostrand']
}
}
case 'insertSizeAndPairOrientation':
break

case 'modifications':
case 'methylation':
// this coloring is similar to igv.js, and is helpful to color negative
// strand reads differently because their c-g will be flipped (e.g. g-c
// read right to left)
return feature.get('flags') & 16 ? '#c8dcc8' : '#c8c8c8'

default:
return defaultColor
? '#c8c8c8'
: readConfObject(config, 'color', { feature })
}
}
83 changes: 83 additions & 0 deletions plugins/alignments/src/PileupRenderer/layoutFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { bpSpanPx, Feature, Region } from '@jbrowse/core/util'
import { BaseLayout } from '@jbrowse/core/util/layouts'
// locals
import { Mismatch } from '../MismatchParser'

export interface LayoutRecord {
feature: Feature
leftPx: number
rightPx: number
topPx: number
heightPx: number
}

export function layoutFeature({
feature,
layout,
bpPerPx,
region,
showSoftClip,
heightPx,
displayMode,
}: {
feature: Feature
layout: BaseLayout<Feature>
bpPerPx: number
region: Region
showSoftClip?: boolean
heightPx: number
displayMode: string
}): LayoutRecord | null {
let expansionBefore = 0
let expansionAfter = 0

// Expand the start and end of feature when softclipping enabled
if (showSoftClip) {
const mismatches = feature.get('mismatches') as Mismatch[]
const seq = feature.get('seq') as string
if (seq) {
for (let i = 0; i < mismatches.length; i += 1) {
const { type, start, cliplen = 0 } = mismatches[i]
if (type === 'softclip') {
start === 0 ? (expansionBefore = cliplen) : (expansionAfter = cliplen)
}
}
}
}

const [leftPx, rightPx] = bpSpanPx(
feature.get('start') - expansionBefore,
feature.get('end') + expansionAfter,
region,
bpPerPx,
)

if (displayMode === 'compact') {
heightPx /= 3
}
if (feature.get('refName') !== region.refName) {
throw new Error(
`feature ${feature.id()} is not on the current region's reference sequence ${
region.refName
}`,
)
}
const topPx = layout.addRect(
feature.id(),
feature.get('start') - expansionBefore,
feature.get('end') + expansionAfter,
heightPx,
feature,
)
if (topPx === null) {
return null
}

return {
feature,
leftPx,
rightPx,
topPx: displayMode === 'collapse' ? 0 : topPx,
heightPx,
}
}
44 changes: 44 additions & 0 deletions plugins/alignments/src/PileupRenderer/layoutFeatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { readConfObject } from '@jbrowse/core/configuration'
import { iterMap } from '@jbrowse/core/util'

// locals
import { layoutFeature } from './layoutFeature'
import { RenderArgsDeserializedWithFeaturesAndLayout } from './PileupRenderer'
import { sortFeature } from './sortUtil'

// layout determines the height of the canvas that we use to render
export function layoutFeats(
props: RenderArgsDeserializedWithFeaturesAndLayout,
) {
const { layout, features, sortedBy, config, bpPerPx, showSoftClip, regions } =
props
const [region] = regions
if (!layout) {
throw new Error(`layout required`)
}
if (!layout.addRect) {
throw new Error('invalid layout object')
}

const featureMap =
sortedBy?.type && region.start === sortedBy.pos
? sortFeature(features, sortedBy)
: features

const heightPx = readConfObject(config, 'height')
const displayMode = readConfObject(config, 'displayMode')
return iterMap(
featureMap.values(),
feature =>
layoutFeature({
feature,
layout,
bpPerPx,
region,
showSoftClip,
heightPx,
displayMode,
}),
featureMap.size,
)
}
93 changes: 93 additions & 0 deletions plugins/alignments/src/PileupRenderer/makeImageData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Feature } from '@jbrowse/core/util'
import { RenderArgsDeserializedWithFeaturesAndLayout } from './PileupRenderer'
import { readConfObject } from '@jbrowse/core/configuration'
import { createJBrowseTheme } from '@jbrowse/core/ui'
import {
getCharWidthHeight,
getColorBaseMap,
getContrastBaseMap,
shouldDrawIndels,
shouldDrawSNPsMuted,
} from './util'
import { renderAlignment } from './renderAlignment'
import { renderMismatches } from './renderMismatches'
import { renderSoftClipping } from './renderSoftClipping'

export interface RenderArgsWithColor
extends RenderArgsDeserializedWithFeaturesAndLayout {
Color: Awaited<typeof import('color')>
}

interface LayoutFeature {
heightPx: number
topPx: number
feature: Feature
}

export function makeImageData({
ctx,
layoutRecords,
canvasWidth,
renderArgs,
}: {
ctx: CanvasRenderingContext2D
canvasWidth: number
layoutRecords: LayoutFeature[]
renderArgs: RenderArgsWithColor
}) {
const { config, showSoftClip, colorBy, theme: configTheme } = renderArgs
const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
const largeInsertionIndicatorScale = readConfObject(
config,
'largeInsertionIndicatorScale',
)
const defaultColor = readConfObject(config, 'color') === '#f0f'
const theme = createJBrowseTheme(configTheme)
const colorForBase = getColorBaseMap(theme)
const contrastForBase = getContrastBaseMap(theme)
ctx.font = 'bold 10px Courier New,monospace'

const { charWidth, charHeight } = getCharWidthHeight()
const drawSNPsMuted = shouldDrawSNPsMuted(colorBy?.type)
const drawIndels = shouldDrawIndels()
for (const feat of layoutRecords) {
renderAlignment({
ctx,
feat,
renderArgs,
defaultColor,
colorForBase,
contrastForBase,
charWidth,
charHeight,
canvasWidth,
})
renderMismatches({
ctx,
feat,
renderArgs,
mismatchAlpha,
drawSNPsMuted,
drawIndels,
largeInsertionIndicatorScale,
minSubfeatureWidth,
charWidth,
charHeight,
colorForBase,
contrastForBase,
canvasWidth,
})
if (showSoftClip) {
renderSoftClipping({
ctx,
feat,
renderArgs,
colorForBase,
config,
theme,
canvasWidth,
})
}
}
}
Loading

0 comments on commit ab283f7

Please sign in to comment.