From 541012f20dcd9f505231a72fb4ac629c8270b2f8 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 15 Nov 2021 22:21:24 -0500 Subject: [PATCH] Save time pileup renderer --- .../src/BamAdapter/BamSlightlyLazyFeature.ts | 28 +++---- .../src/BamAdapter/MismatchParser.ts | 11 +-- .../src/PileupRenderer/PileupRenderer.tsx | 74 ++++++++++++------- .../src/PileupRenderer/configSchema.ts | 7 -- .../ConfigurationEditor.test.js.snap | 62 ---------------- 5 files changed, 61 insertions(+), 121 deletions(-) diff --git a/plugins/alignments/src/BamAdapter/BamSlightlyLazyFeature.ts b/plugins/alignments/src/BamAdapter/BamSlightlyLazyFeature.ts index e3c1c6b8f1..34d6d15205 100644 --- a/plugins/alignments/src/BamAdapter/BamSlightlyLazyFeature.ts +++ b/plugins/alignments/src/BamAdapter/BamSlightlyLazyFeature.ts @@ -153,16 +153,12 @@ export default class BamSlightlyLazyFeature implements Feature { } toJSON(): SimpleFeatureSerialized { - const tags = Object.fromEntries( - this.tags() - .map(t => { - return [t, this.get(t)] - }) - .filter(elt => elt[1] !== undefined), - ) - return { - ...tags, + ...Object.fromEntries( + this.tags() + .map(t => [t, this.get(t)]) + .filter(elt => elt[1] !== undefined), + ), uniqueId: this.id(), } } @@ -201,24 +197,18 @@ export default class BamSlightlyLazyFeature implements Feature { // parse the CIGAR tag if it has one const cigarString = this.get(cigarAttributeName) + const seq = this.get('seq') + const qual = this.qualRaw() if (cigarString) { cigarOps = parseCigar(cigarString) - mismatches = mismatches.concat( - cigarToMismatches(cigarOps, this.get('seq'), this.qualRaw()), - ) + mismatches = mismatches.concat(cigarToMismatches(cigarOps, seq, qual)) } // now let's look for CRAM or MD mismatches const mdString = this.get(mdAttributeName) if (mdString) { mismatches = mismatches.concat( - mdToMismatches( - mdString, - cigarOps, - mismatches, - this.get('seq'), - this.qualRaw(), - ), + mdToMismatches(mdString, cigarOps, mismatches, seq, qual), ) } diff --git a/plugins/alignments/src/BamAdapter/MismatchParser.ts b/plugins/alignments/src/BamAdapter/MismatchParser.ts index c0712d0ec0..6386a09de1 100644 --- a/plugins/alignments/src/BamAdapter/MismatchParser.ts +++ b/plugins/alignments/src/BamAdapter/MismatchParser.ts @@ -150,18 +150,19 @@ export function mdToMismatches( } // now actually parse the MD string - const md = mdstring.match(/(\d+|\^[a-z]+|[a-z])/gi) || [] + const md = mdstring.match(/(\d+|\^[A-Za-z]+|[A-Za-z])/g) || [] for (let i = 0; i < md.length; i++) { const token = md[i] - if (token.match(/^\d/)) { - curr.start += parseInt(token, 10) - } else if (token.match(/^\^/)) { + const num = +token + if (!Number.isNaN(num)) { + curr.start += num + } else if (token.startsWith('^')) { curr.length = token.length - 1 curr.base = '*' curr.type = 'deletion' curr.seq = token.substring(1) nextRecord() - } else if (token.match(/^[a-z]/i)) { + } else { // mismatch for (let j = 0; j < token.length; j += 1) { curr.length = 1 diff --git a/plugins/alignments/src/PileupRenderer/PileupRenderer.tsx b/plugins/alignments/src/PileupRenderer/PileupRenderer.tsx index 8dfeb52c30..919c614f1d 100644 --- a/plugins/alignments/src/PileupRenderer/PileupRenderer.tsx +++ b/plugins/alignments/src/PileupRenderer/PileupRenderer.tsx @@ -18,6 +18,8 @@ import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils' import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout' import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache' import { readConfObject } from '@jbrowse/core/configuration' + +// locals import { Mismatch, parseCigar, @@ -26,21 +28,12 @@ import { } from '../BamAdapter/MismatchParser' import { sortFeature } from './sortUtil' import { getTagAlt, orientationTypes } from '../util' - import { PileupLayoutSession, PileupLayoutSessionProps, } from './PileupLayoutSession' import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter' -export type { - RenderArgs, - RenderArgsSerialized, - RenderResults, - ResultsSerialized, - ResultsDeserialized, -} - function getColorBaseMap(theme: Theme) { return { A: theme.palette.bases.A.main, @@ -94,7 +87,7 @@ const alignmentColoring: { [key: string]: string } = { color_pair_rr: 'navy', color_pair_rl: 'teal', color_pair_ll: 'green', - color_nostrand: '#999', + color_nostrand: '#c8c8c8', color_interchrom: 'orange', color_longinsert: 'red', color_shortinsert: 'pink', @@ -121,21 +114,30 @@ export default class PileupRenderer extends BoxRendererType { return { charWidth, charHeight } } - layoutFeature( - feature: Feature, - layout: BaseLayout, - config: AnyConfigurationModel, - bpPerPx: number, - region: Region, - showSoftClip?: boolean, - ): LayoutRecord | null { + layoutFeature({ + feature, + layout, + bpPerPx, + region, + showSoftClip, + heightPx, + displayMode, + }: { + feature: Feature + layout: BaseLayout + bpPerPx: number + region: Region + showSoftClip?: boolean + heightPx: number + displayMode: string + }): LayoutRecord | null { let expansionBefore = 0 let expansionAfter = 0 - const mismatches: Mismatch[] = feature.get('mismatches') const seq: string = feature.get('seq') // Expand the start and end of feature when softclipping enabled if (showSoftClip && seq) { + const mismatches: Mismatch[] = feature.get('mismatches') for (let i = 0; i < mismatches.length; i += 1) { const { type, start, cliplen = 0 } = mismatches[i] if (type === 'softclip') { @@ -151,8 +153,6 @@ export default class PileupRenderer extends BoxRendererType { bpPerPx, ) - let heightPx = readConfObject(config, 'height', { feature }) - const displayMode = readConfObject(config, 'displayMode', { feature }) if (displayMode === 'compact') { heightPx /= 3 } @@ -554,7 +554,7 @@ export default class PileupRenderer extends BoxRendererType { // fetchValues else { const foundValue = colorTagMap[val] - ctx.fillStyle = foundValue || 'color_nostrand' + ctx.fillStyle = foundValue || alignmentColoring['color_nostrand'] } break } @@ -563,7 +563,7 @@ export default class PileupRenderer extends BoxRendererType { case 'normal': default: - ctx.fillStyle = readConfObject(config, 'color', { feature }) + ctx.fillStyle = '#c8c8c8' break } @@ -598,6 +598,8 @@ export default class PileupRenderer extends BoxRendererType { drawIndels?: boolean minSubfeatureWidth: number largeInsertionIndicatorScale: number + charWidth: number + charHeight: number }, ) { const { @@ -606,10 +608,11 @@ export default class PileupRenderer extends BoxRendererType { mismatchAlpha, drawSNPs = true, drawIndels = true, + charWidth, + charHeight, } = opts const { bpPerPx, regions } = props const { heightPx, topPx, feature } = feat - const { charWidth, charHeight } = this.getCharWidthHeight(ctx) const [region] = regions const start = feature.get('start') @@ -836,6 +839,8 @@ export default class PileupRenderer extends BoxRendererType { throw new Error('invalid layout object') } ctx.font = 'bold 10px Courier New,monospace' + + const { charWidth, charHeight } = this.getCharWidthHeight(ctx) // eslint-disable-next-line @typescript-eslint/no-explicit-any layoutRecords.forEach((feat: any) => { if (feat === null) { @@ -844,7 +849,6 @@ export default class PileupRenderer extends BoxRendererType { const { feature, topPx, heightPx } = feat - ctx.fillStyle = readConfObject(config, 'color', { feature }) this.drawAlignmentRect(ctx, { feature, topPx, heightPx }, props) this.drawMismatches(ctx, feat, props, theme, colorForBase, { mismatchAlpha, @@ -852,6 +856,8 @@ export default class PileupRenderer extends BoxRendererType { drawIndels: shouldDrawMismatches(colorBy?.type), largeInsertionIndicatorScale: insertScale, minSubfeatureWidth, + charWidth, + charHeight, }) if (showSoftClip) { this.drawSoftClipping(ctx, feat, props, config, theme) @@ -884,17 +890,21 @@ export default class PileupRenderer extends BoxRendererType { ? sortFeature(features, sortedBy) : null const featureMap = sortedFeatures || features + + const heightPx = readConfObject(config, 'height') + const displayMode = readConfObject(config, 'displayMode') const layoutRecords = iterMap( featureMap.values(), feature => - this.layoutFeature( + this.layoutFeature({ feature, layout, - config, bpPerPx, region, showSoftClip, - ), + heightPx, + displayMode, + }), featureMap.size, ) return layoutRecords @@ -971,3 +981,11 @@ export default class PileupRenderer extends BoxRendererType { return new PileupLayoutSession(args) } } + +export type { + RenderArgs, + RenderArgsSerialized, + RenderResults, + ResultsSerialized, + ResultsDeserialized, +} diff --git a/plugins/alignments/src/PileupRenderer/configSchema.ts b/plugins/alignments/src/PileupRenderer/configSchema.ts index bd4779ec5b..a02ef91606 100644 --- a/plugins/alignments/src/PileupRenderer/configSchema.ts +++ b/plugins/alignments/src/PileupRenderer/configSchema.ts @@ -4,13 +4,6 @@ import { types } from 'mobx-state-tree' export default ConfigurationSchema( 'PileupRenderer', { - color: { - type: 'color', - description: 'the color of each feature in a pileup alignment', - defaultValue: `#c8c8c8`, - contextVariable: ['feature'], - }, - orientationType: { type: 'stringEnum', model: types.enumeration('orientationType', ['fr', 'rf', 'ff']), diff --git a/plugins/config/src/ConfigurationEditorWidget/components/__snapshots__/ConfigurationEditor.test.js.snap b/plugins/config/src/ConfigurationEditorWidget/components/__snapshots__/ConfigurationEditor.test.js.snap index 04b440c2d9..6503e14ef3 100644 --- a/plugins/config/src/ConfigurationEditorWidget/components/__snapshots__/ConfigurationEditor.test.js.snap +++ b/plugins/config/src/ConfigurationEditorWidget/components/__snapshots__/ConfigurationEditor.test.js.snap @@ -975,68 +975,6 @@ exports[`ConfigurationEditor widget renders with defaults of the PileupTrack sch
-
-
-
- -
- -
-

- the color of each feature in a pileup alignment -

-
-
-
- -
-