diff --git a/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts b/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts index 29768f80a0..a7746ff5a4 100644 --- a/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts +++ b/plugins/alignments/src/LinearPileupDisplay/SharedLinearPileupDisplayMixin.ts @@ -98,6 +98,10 @@ export function SharedLinearPileupDisplayMixin( * #property */ jexlFilters: types.optional(types.array(types.string), []), + /** + * #property + */ + hideSmallIndelsSetting: types.maybe(types.boolean), }), ) .volatile(() => ({ @@ -130,10 +134,20 @@ export function SharedLinearPileupDisplayMixin( }, })) .views(self => ({ + /** + * #getter + */ get autorunReady() { const view = getContainingView(self) as LGV return view.initialized && self.statsReadyAndRegionNotTooLarge }, + + /** + * #getter + */ + get hideSmallIndels() { + return self.hideSmallIndelsSetting + }, })) .actions(self => ({ /** @@ -265,6 +279,13 @@ export function SharedLinearPileupDisplayMixin( setJexlFilters(filters: string[]) { self.jexlFilters = cast(filters) }, + + /** + * #action + */ + setHideSmallIndels(arg: boolean) { + self.hideSmallIndelsSetting = arg + }, })) .views(self => ({ @@ -272,17 +293,21 @@ export function SharedLinearPileupDisplayMixin( * #getter */ get rendererConfig() { - const { featureHeight, noSpacing, trackMaxHeight, rendererTypeName } = - self + const { + featureHeight: height, + noSpacing, + hideSmallIndels, + trackMaxHeight: maxHeight, + rendererTypeName, + } = self const configBlob = getConf(self, ['renderers', rendererTypeName]) || {} return self.rendererType.configSchema.create( { ...configBlob, - ...(featureHeight !== undefined ? { height: featureHeight } : {}), + ...(hideSmallIndels !== undefined ? { hideSmallIndels } : {}), + ...(height !== undefined ? { height } : {}), ...(noSpacing !== undefined ? { noSpacing } : {}), - ...(trackMaxHeight !== undefined - ? { maxHeight: trackMaxHeight } - : {}), + ...(maxHeight !== undefined ? { maxHeight } : {}), }, getEnv(self), ) @@ -561,6 +586,15 @@ export function SharedLinearPileupDisplayMixin( }, ], }, + { + label: 'Hide small indels (<10bp)', + priority: -1, + type: 'checkbox', + checked: self.hideSmallIndels, + onClick: () => { + self.setHideSmallIndels(!self.hideSmallIndels) + }, + }, { label: 'Set max height...', priority: -1, diff --git a/plugins/alignments/src/LinearPileupDisplay/model.ts b/plugins/alignments/src/LinearPileupDisplay/model.ts index d374811f9a..07a47e580d 100644 --- a/plugins/alignments/src/LinearPileupDisplay/model.ts +++ b/plugins/alignments/src/LinearPileupDisplay/model.ts @@ -208,12 +208,14 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) { trackMaxHeight, mismatchAlpha, rendererTypeName, + hideSmallIndels, } = self const configBlob = getConf(self, ['renderers', rendererTypeName]) || {} return self.rendererType.configSchema.create( { ...configBlob, ...(featureHeight !== undefined ? { height: featureHeight } : {}), + ...(hideSmallIndels !== undefined ? { hideSmallIndels } : {}), ...(noSpacing !== undefined ? { noSpacing } : {}), ...(mismatchAlpha !== undefined ? { mismatchAlpha } : {}), ...(trackMaxHeight !== undefined diff --git a/plugins/alignments/src/PileupRenderer/configSchema.ts b/plugins/alignments/src/PileupRenderer/configSchema.ts index 046922a7cd..65c3a23356 100644 --- a/plugins/alignments/src/PileupRenderer/configSchema.ts +++ b/plugins/alignments/src/PileupRenderer/configSchema.ts @@ -52,6 +52,15 @@ const PileupRenderer = ConfigurationSchema( 'the minimum width in px for a pileup mismatch feature. use for increasing/decreasing mismatch marker widths when zoomed out, e.g. 0 or 1', defaultValue: 1, }, + /** + * #slot + */ + hideSmallIndels: { + type: 'boolean', + description: + 'Hides small indels, sometimes occurring in long read sequencing', + defaultValue: false, + }, /** * #slot */ diff --git a/plugins/alignments/src/PileupRenderer/makeImageData.ts b/plugins/alignments/src/PileupRenderer/makeImageData.ts index c9626af43d..ffb0b2b31b 100644 --- a/plugins/alignments/src/PileupRenderer/makeImageData.ts +++ b/plugins/alignments/src/PileupRenderer/makeImageData.ts @@ -46,6 +46,7 @@ export function makeImageData({ config, 'largeInsertionIndicatorScale', ) + const hideSmallIndels = readConfObject(config, 'hideSmallIndels') as boolean const defaultColor = readConfObject(config, 'color') === '#f0f' const theme = createJBrowseTheme(configTheme) const colorForBase = getColorBaseMap(theme) @@ -76,6 +77,7 @@ export function makeImageData({ ctx, feat, renderArgs, + hideSmallIndels, mismatchAlpha, drawSNPsMuted, drawIndels, diff --git a/plugins/alignments/src/PileupRenderer/renderMismatches.ts b/plugins/alignments/src/PileupRenderer/renderMismatches.ts index 2192575cbb..c54818c7d3 100644 --- a/plugins/alignments/src/PileupRenderer/renderMismatches.ts +++ b/plugins/alignments/src/PileupRenderer/renderMismatches.ts @@ -18,6 +18,7 @@ export function renderMismatches({ charHeight, colorForBase, contrastForBase, + hideSmallIndels, canvasWidth, drawSNPsMuted, drawIndels = true, @@ -32,6 +33,7 @@ export function renderMismatches({ drawSNPsMuted?: boolean minSubfeatureWidth: number largeInsertionIndicatorScale: number + hideSmallIndels: boolean charWidth: number charHeight: number canvasWidth: number @@ -100,32 +102,41 @@ export function renderMismatches({ ) } } else if (mismatch.type === 'deletion' && drawIndels) { - fillRect( - ctx, - leftPx, - topPx, - Math.abs(leftPx - rightPx), - heightPx, - canvasWidth, - colorForBase.deletion, - ) - const txt = `${mismatch.length}` - const rwidth = measureText(txt, 10) - if (widthPx >= rwidth && heightPx >= heightLim) { - ctx.fillStyle = contrastForBase.deletion! - ctx.fillText(txt, (leftPx + rightPx) / 2 - rwidth / 2, topPx + heightPx) + const len = mismatch.length + if (!hideSmallIndels || len >= 10) { + fillRect( + ctx, + leftPx, + topPx, + Math.abs(leftPx - rightPx), + heightPx, + canvasWidth, + colorForBase.deletion, + ) + const txt = `${mismatch.length}` + const rwidth = measureText(txt, 10) + if (widthPx >= rwidth && heightPx >= heightLim) { + ctx.fillStyle = contrastForBase.deletion! + ctx.fillText( + txt, + (leftPx + rightPx) / 2 - rwidth / 2, + topPx + heightPx, + ) + } } } else if (mismatch.type === 'insertion' && drawIndels) { const pos = leftPx + extraHorizontallyFlippedOffset const len = +mismatch.base || mismatch.length const insW = Math.max(minSubfeatureWidth, Math.min(1.2, 1 / bpPerPx)) if (len < 10) { - fillRect(ctx, pos, topPx, insW, heightPx, canvasWidth, 'purple') - if (1 / bpPerPx >= charWidth && heightPx >= heightLim) { - const l = Math.round(pos - insW) - fillRect(ctx, l, topPx, insW * 3, 1, canvasWidth) - fillRect(ctx, l, topPx + heightPx - 1, insW * 3, 1, canvasWidth) - ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx) + if (!hideSmallIndels) { + fillRect(ctx, pos, topPx, insW, heightPx, canvasWidth, 'purple') + if (1 / bpPerPx >= charWidth && heightPx >= heightLim) { + const l = Math.round(pos - insW) + fillRect(ctx, l, topPx, insW * 3, 1, canvasWidth) + fillRect(ctx, l, topPx + heightPx - 1, insW * 3, 1, canvasWidth) + ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx) + } } } } else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {