@jbrowse/plugin-alignments 1.7.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AlignmentsFeatureDetail/AlignmentsFeatureDetail.js +192 -207
- package/dist/AlignmentsFeatureDetail/AlignmentsFeatureDetail.js.map +1 -0
- package/dist/AlignmentsFeatureDetail/index.d.ts +28 -3
- package/dist/AlignmentsFeatureDetail/index.js +48 -55
- package/dist/AlignmentsFeatureDetail/index.js.map +1 -0
- package/dist/AlignmentsTrack/index.js +24 -32
- package/dist/AlignmentsTrack/index.js.map +1 -0
- package/dist/BamAdapter/BamAdapter.js +345 -585
- package/dist/BamAdapter/BamAdapter.js.map +1 -0
- package/dist/BamAdapter/BamSlightlyLazyFeature.js +143 -174
- package/dist/BamAdapter/BamSlightlyLazyFeature.js.map +1 -0
- package/dist/BamAdapter/MismatchParser.js +340 -416
- package/dist/BamAdapter/MismatchParser.js.map +1 -0
- package/dist/BamAdapter/configSchema.js +33 -46
- package/dist/BamAdapter/configSchema.js.map +1 -0
- package/dist/BamAdapter/index.js +36 -32
- package/dist/BamAdapter/index.js.map +1 -0
- package/dist/CramAdapter/CramAdapter.js +376 -644
- package/dist/CramAdapter/CramAdapter.js.map +1 -0
- package/dist/CramAdapter/CramSlightlyLazyFeature.js +374 -439
- package/dist/CramAdapter/CramSlightlyLazyFeature.js.map +1 -0
- package/dist/CramAdapter/CramTestAdapters.js +169 -227
- package/dist/CramAdapter/CramTestAdapters.js.map +1 -0
- package/dist/CramAdapter/configSchema.js +28 -38
- package/dist/CramAdapter/configSchema.js.map +1 -0
- package/dist/CramAdapter/index.js +37 -32
- package/dist/CramAdapter/index.js.map +1 -0
- package/dist/HtsgetBamAdapter/HtsgetBamAdapter.js +91 -93
- package/dist/HtsgetBamAdapter/HtsgetBamAdapter.js.map +1 -0
- package/dist/HtsgetBamAdapter/configSchema.js +19 -29
- package/dist/HtsgetBamAdapter/configSchema.js.map +1 -0
- package/dist/HtsgetBamAdapter/index.js +44 -38
- package/dist/HtsgetBamAdapter/index.js.map +1 -0
- package/dist/LinearAlignmentsDisplay/components/AlignmentsDisplay.js +36 -65
- package/dist/LinearAlignmentsDisplay/components/AlignmentsDisplay.js.map +1 -0
- package/dist/LinearAlignmentsDisplay/index.js +22 -28
- package/dist/LinearAlignmentsDisplay/index.js.map +1 -0
- package/dist/LinearAlignmentsDisplay/models/configSchema.js +12 -23
- package/dist/LinearAlignmentsDisplay/models/configSchema.js.map +1 -0
- package/dist/LinearAlignmentsDisplay/models/model.d.ts +10 -10
- package/dist/LinearAlignmentsDisplay/models/model.js +257 -245
- package/dist/LinearAlignmentsDisplay/models/model.js.map +1 -0
- package/dist/LinearPileupDisplay/components/ColorByModifications.js +98 -116
- package/dist/LinearPileupDisplay/components/ColorByModifications.js.map +1 -0
- package/dist/LinearPileupDisplay/components/ColorByTag.js +82 -91
- package/dist/LinearPileupDisplay/components/ColorByTag.js.map +1 -0
- package/dist/LinearPileupDisplay/components/FilterByTag.js +156 -192
- package/dist/LinearPileupDisplay/components/FilterByTag.js.map +1 -0
- package/dist/LinearPileupDisplay/components/LinearPileupDisplayBlurb.js +15 -29
- package/dist/LinearPileupDisplay/components/LinearPileupDisplayBlurb.js.map +1 -0
- package/dist/LinearPileupDisplay/components/SetFeatureHeight.js +79 -93
- package/dist/LinearPileupDisplay/components/SetFeatureHeight.js.map +1 -0
- package/dist/LinearPileupDisplay/components/SetMaxHeight.js +78 -81
- package/dist/LinearPileupDisplay/components/SetMaxHeight.js.map +1 -0
- package/dist/LinearPileupDisplay/components/SortByTag.js +80 -88
- package/dist/LinearPileupDisplay/components/SortByTag.js.map +1 -0
- package/dist/LinearPileupDisplay/configSchema.js +40 -42
- package/dist/LinearPileupDisplay/configSchema.js.map +1 -0
- package/dist/LinearPileupDisplay/index.js +21 -27
- package/dist/LinearPileupDisplay/index.js.map +1 -0
- package/dist/LinearPileupDisplay/model.d.ts +33 -20
- package/dist/LinearPileupDisplay/model.js +702 -716
- package/dist/LinearPileupDisplay/model.js.map +1 -0
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +105 -57
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js.map +1 -0
- package/dist/LinearSNPCoverageDisplay/index.js +21 -27
- package/dist/LinearSNPCoverageDisplay/index.js.map +1 -0
- package/dist/LinearSNPCoverageDisplay/models/configSchema.js +45 -55
- package/dist/LinearSNPCoverageDisplay/models/configSchema.js.map +1 -0
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +14 -12
- package/dist/LinearSNPCoverageDisplay/models/model.js +257 -230
- package/dist/LinearSNPCoverageDisplay/models/model.js.map +1 -0
- package/dist/NestedFrequencyTable.js +104 -139
- package/dist/NestedFrequencyTable.js.map +1 -0
- package/dist/PileupRPC/rpcMethods.js +199 -278
- package/dist/PileupRPC/rpcMethods.js.map +1 -0
- package/dist/PileupRenderer/PileupLayoutSession.js +56 -76
- package/dist/PileupRenderer/PileupLayoutSession.js.map +1 -0
- package/dist/PileupRenderer/PileupRenderer.d.ts +56 -11
- package/dist/PileupRenderer/PileupRenderer.js +942 -1134
- package/dist/PileupRenderer/PileupRenderer.js.map +1 -0
- package/dist/PileupRenderer/components/PileupRendering.d.ts +1 -1
- package/dist/PileupRenderer/components/PileupRendering.js +173 -253
- package/dist/PileupRenderer/components/PileupRendering.js.map +1 -0
- package/dist/PileupRenderer/configSchema.js +65 -71
- package/dist/PileupRenderer/configSchema.js.map +1 -0
- package/dist/PileupRenderer/index.js +17 -22
- package/dist/PileupRenderer/index.js.map +1 -0
- package/dist/PileupRenderer/sortUtil.js +83 -107
- package/dist/PileupRenderer/sortUtil.js.map +1 -0
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +2 -0
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +436 -586
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js.map +1 -0
- package/dist/SNPCoverageAdapter/configSchema.js +10 -20
- package/dist/SNPCoverageAdapter/configSchema.js.map +1 -0
- package/dist/SNPCoverageAdapter/index.js +46 -41
- package/dist/SNPCoverageAdapter/index.js.map +1 -0
- package/dist/SNPCoverageRenderer/SNPCoverageRenderer.d.ts +1 -1
- package/dist/SNPCoverageRenderer/SNPCoverageRenderer.js +265 -290
- package/dist/SNPCoverageRenderer/SNPCoverageRenderer.js.map +1 -0
- package/dist/SNPCoverageRenderer/configSchema.js +30 -39
- package/dist/SNPCoverageRenderer/configSchema.js.map +1 -0
- package/dist/SNPCoverageRenderer/index.js +19 -30
- package/dist/SNPCoverageRenderer/index.js.map +1 -0
- package/dist/index.js +135 -152
- package/dist/index.js.map +1 -0
- package/dist/shared.js +84 -92
- package/dist/shared.js.map +1 -0
- package/dist/util.js +130 -121
- package/dist/util.js.map +1 -0
- package/esm/AlignmentsFeatureDetail/AlignmentsFeatureDetail.d.ts +6 -0
- package/esm/AlignmentsFeatureDetail/AlignmentsFeatureDetail.js +145 -0
- package/esm/AlignmentsFeatureDetail/AlignmentsFeatureDetail.js.map +1 -0
- package/esm/AlignmentsFeatureDetail/index.d.ts +38 -0
- package/esm/AlignmentsFeatureDetail/index.js +23 -0
- package/esm/AlignmentsFeatureDetail/index.js.map +1 -0
- package/esm/AlignmentsTrack/index.d.ts +2 -0
- package/esm/AlignmentsTrack/index.js +23 -0
- package/esm/AlignmentsTrack/index.js.map +1 -0
- package/esm/BamAdapter/BamAdapter.d.ts +40 -0
- package/esm/BamAdapter/BamAdapter.js +173 -0
- package/esm/BamAdapter/BamAdapter.js.map +1 -0
- package/esm/BamAdapter/BamSlightlyLazyFeature.d.ts +33 -0
- package/esm/BamAdapter/BamSlightlyLazyFeature.js +107 -0
- package/esm/BamAdapter/BamSlightlyLazyFeature.js.map +1 -0
- package/esm/BamAdapter/MismatchParser.d.ts +25 -0
- package/esm/BamAdapter/MismatchParser.js +294 -0
- package/esm/BamAdapter/MismatchParser.js.map +1 -0
- package/esm/BamAdapter/configSchema.d.ts +2 -0
- package/esm/BamAdapter/configSchema.js +31 -0
- package/esm/BamAdapter/configSchema.js.map +1 -0
- package/esm/BamAdapter/index.d.ts +3 -0
- package/esm/BamAdapter/index.js +10 -0
- package/esm/BamAdapter/index.js.map +1 -0
- package/esm/CramAdapter/CramAdapter.d.ts +53 -0
- package/esm/CramAdapter/CramAdapter.js +228 -0
- package/esm/CramAdapter/CramAdapter.js.map +1 -0
- package/esm/CramAdapter/CramSlightlyLazyFeature.d.ts +49 -0
- package/esm/CramAdapter/CramSlightlyLazyFeature.js +349 -0
- package/esm/CramAdapter/CramSlightlyLazyFeature.js.map +1 -0
- package/esm/CramAdapter/CramTestAdapters.d.ts +29 -0
- package/esm/CramAdapter/CramTestAdapters.js +70 -0
- package/esm/CramAdapter/CramTestAdapters.js.map +1 -0
- package/esm/CramAdapter/configSchema.d.ts +3 -0
- package/esm/CramAdapter/configSchema.js +26 -0
- package/esm/CramAdapter/configSchema.js.map +1 -0
- package/esm/CramAdapter/index.d.ts +3 -0
- package/esm/CramAdapter/index.js +11 -0
- package/esm/CramAdapter/index.js.map +1 -0
- package/esm/HtsgetBamAdapter/HtsgetBamAdapter.d.ts +9 -0
- package/esm/HtsgetBamAdapter/HtsgetBamAdapter.js +27 -0
- package/esm/HtsgetBamAdapter/HtsgetBamAdapter.js.map +1 -0
- package/esm/HtsgetBamAdapter/configSchema.d.ts +2 -0
- package/esm/HtsgetBamAdapter/configSchema.js +17 -0
- package/esm/HtsgetBamAdapter/configSchema.js.map +1 -0
- package/esm/HtsgetBamAdapter/index.d.ts +3 -0
- package/esm/HtsgetBamAdapter/index.js +16 -0
- package/esm/HtsgetBamAdapter/index.js.map +1 -0
- package/esm/LinearAlignmentsDisplay/components/AlignmentsDisplay.d.ts +7 -0
- package/esm/LinearAlignmentsDisplay/components/AlignmentsDisplay.js +34 -0
- package/esm/LinearAlignmentsDisplay/components/AlignmentsDisplay.js.map +1 -0
- package/esm/LinearAlignmentsDisplay/index.d.ts +2 -0
- package/esm/LinearAlignmentsDisplay/index.js +19 -0
- package/esm/LinearAlignmentsDisplay/index.js.map +1 -0
- package/esm/LinearAlignmentsDisplay/models/configSchema.d.ts +4 -0
- package/esm/LinearAlignmentsDisplay/models/configSchema.js +12 -0
- package/esm/LinearAlignmentsDisplay/models/configSchema.js.map +1 -0
- package/esm/LinearAlignmentsDisplay/models/model.d.ts +105 -0
- package/esm/LinearAlignmentsDisplay/models/model.js +181 -0
- package/esm/LinearAlignmentsDisplay/models/model.js.map +1 -0
- package/esm/LinearPileupDisplay/components/ColorByModifications.d.ts +14 -0
- package/esm/LinearPileupDisplay/components/ColorByModifications.js +71 -0
- package/esm/LinearPileupDisplay/components/ColorByModifications.js.map +1 -0
- package/esm/LinearPileupDisplay/components/ColorByTag.d.ts +9 -0
- package/esm/LinearPileupDisplay/components/ColorByTag.js +45 -0
- package/esm/LinearPileupDisplay/components/ColorByTag.js.map +1 -0
- package/esm/LinearPileupDisplay/components/FilterByTag.d.ts +18 -0
- package/esm/LinearPileupDisplay/components/FilterByTag.js +123 -0
- package/esm/LinearPileupDisplay/components/FilterByTag.js.map +1 -0
- package/esm/LinearPileupDisplay/components/LinearPileupDisplayBlurb.d.ts +13 -0
- package/esm/LinearPileupDisplay/components/LinearPileupDisplayBlurb.js +13 -0
- package/esm/LinearPileupDisplay/components/LinearPileupDisplayBlurb.js.map +1 -0
- package/esm/LinearPileupDisplay/components/SetFeatureHeight.d.ts +16 -0
- package/esm/LinearPileupDisplay/components/SetFeatureHeight.js +41 -0
- package/esm/LinearPileupDisplay/components/SetFeatureHeight.js.map +1 -0
- package/esm/LinearPileupDisplay/components/SetMaxHeight.d.ts +10 -0
- package/esm/LinearPileupDisplay/components/SetMaxHeight.js +43 -0
- package/esm/LinearPileupDisplay/components/SetMaxHeight.js.map +1 -0
- package/esm/LinearPileupDisplay/components/SortByTag.d.ts +9 -0
- package/esm/LinearPileupDisplay/components/SortByTag.js +43 -0
- package/esm/LinearPileupDisplay/components/SortByTag.js.map +1 -0
- package/esm/LinearPileupDisplay/configSchema.d.ts +6 -0
- package/esm/LinearPileupDisplay/configSchema.js +41 -0
- package/esm/LinearPileupDisplay/configSchema.js.map +1 -0
- package/esm/LinearPileupDisplay/index.d.ts +2 -0
- package/esm/LinearPileupDisplay/index.js +18 -0
- package/esm/LinearPileupDisplay/index.js.map +1 -0
- package/esm/LinearPileupDisplay/model.d.ts +332 -0
- package/esm/LinearPileupDisplay/model.js +576 -0
- package/esm/LinearPileupDisplay/model.js.map +1 -0
- package/esm/LinearSNPCoverageDisplay/components/Tooltip.d.ts +10 -0
- package/esm/LinearSNPCoverageDisplay/components/Tooltip.js +57 -0
- package/esm/LinearSNPCoverageDisplay/components/Tooltip.js.map +1 -0
- package/esm/LinearSNPCoverageDisplay/index.d.ts +2 -0
- package/esm/LinearSNPCoverageDisplay/index.js +18 -0
- package/esm/LinearSNPCoverageDisplay/index.js.map +1 -0
- package/esm/LinearSNPCoverageDisplay/models/configSchema.d.ts +2 -0
- package/esm/LinearSNPCoverageDisplay/models/configSchema.js +44 -0
- package/esm/LinearSNPCoverageDisplay/models/configSchema.js.map +1 -0
- package/esm/LinearSNPCoverageDisplay/models/model.d.ts +348 -0
- package/esm/LinearSNPCoverageDisplay/models/model.js +185 -0
- package/esm/LinearSNPCoverageDisplay/models/model.js.map +1 -0
- package/esm/NestedFrequencyTable.d.ts +14 -0
- package/esm/NestedFrequencyTable.js +101 -0
- package/esm/NestedFrequencyTable.js.map +1 -0
- package/esm/PileupRPC/rpcMethods.d.ts +34 -0
- package/esm/PileupRPC/rpcMethods.js +70 -0
- package/esm/PileupRPC/rpcMethods.js.map +1 -0
- package/esm/PileupRenderer/PileupLayoutSession.d.ts +32 -0
- package/esm/PileupRenderer/PileupLayoutSession.js +32 -0
- package/esm/PileupRenderer/PileupLayoutSession.js.map +1 -0
- package/esm/PileupRenderer/PileupRenderer.d.ts +182 -0
- package/esm/PileupRenderer/PileupRenderer.js +830 -0
- package/esm/PileupRenderer/PileupRenderer.js.map +1 -0
- package/esm/PileupRenderer/components/PileupRendering.d.ts +23 -0
- package/esm/PileupRenderer/components/PileupRendering.js +138 -0
- package/esm/PileupRenderer/components/PileupRendering.js.map +1 -0
- package/esm/PileupRenderer/configSchema.d.ts +2 -0
- package/esm/PileupRenderer/configSchema.js +64 -0
- package/esm/PileupRenderer/configSchema.js.map +1 -0
- package/esm/PileupRenderer/index.d.ts +2 -0
- package/esm/PileupRenderer/index.js +12 -0
- package/esm/PileupRenderer/index.js.map +1 -0
- package/esm/PileupRenderer/sortUtil.d.ts +8 -0
- package/esm/PileupRenderer/sortUtil.js +80 -0
- package/esm/PileupRenderer/sortUtil.js.map +1 -0
- package/esm/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +67 -0
- package/esm/SNPCoverageAdapter/SNPCoverageAdapter.js +259 -0
- package/esm/SNPCoverageAdapter/SNPCoverageAdapter.js.map +1 -0
- package/esm/SNPCoverageAdapter/configSchema.d.ts +3 -0
- package/esm/SNPCoverageAdapter/configSchema.js +6 -0
- package/esm/SNPCoverageAdapter/configSchema.js.map +1 -0
- package/esm/SNPCoverageAdapter/index.d.ts +3 -0
- package/esm/SNPCoverageAdapter/index.js +18 -0
- package/esm/SNPCoverageAdapter/index.js.map +1 -0
- package/esm/SNPCoverageRenderer/SNPCoverageRenderer.d.ts +20 -0
- package/esm/SNPCoverageRenderer/SNPCoverageRenderer.js +185 -0
- package/esm/SNPCoverageRenderer/SNPCoverageRenderer.js.map +1 -0
- package/esm/SNPCoverageRenderer/configSchema.d.ts +2 -0
- package/esm/SNPCoverageRenderer/configSchema.js +29 -0
- package/esm/SNPCoverageRenderer/configSchema.js.map +1 -0
- package/esm/SNPCoverageRenderer/index.d.ts +3 -0
- package/esm/SNPCoverageRenderer/index.js +14 -0
- package/esm/SNPCoverageRenderer/index.js.map +1 -0
- package/esm/index.d.ts +10 -0
- package/esm/index.js +91 -0
- package/esm/index.js.map +1 -0
- package/esm/shared.d.ts +25 -0
- package/esm/shared.js +28 -0
- package/esm/shared.js.map +1 -0
- package/esm/util.d.ts +19 -0
- package/esm/util.js +83 -0
- package/esm/util.js.map +1 -0
- package/package.json +19 -11
- package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +16 -6
- package/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap +321 -397
- package/src/AlignmentsFeatureDetail/index.ts +7 -17
- package/src/BamAdapter/MismatchParser.ts +1 -0
- package/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx +3 -3
- package/src/LinearPileupDisplay/components/ColorByModifications.tsx +7 -7
- package/src/LinearPileupDisplay/components/ColorByTag.tsx +5 -5
- package/src/LinearPileupDisplay/components/FilterByTag.tsx +5 -5
- package/src/LinearPileupDisplay/components/LinearPileupDisplayBlurb.tsx +1 -1
- package/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +9 -9
- package/src/LinearPileupDisplay/components/SetMaxHeight.tsx +5 -5
- package/src/LinearPileupDisplay/components/SortByTag.tsx +5 -5
- package/src/LinearPileupDisplay/model.ts +90 -32
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +44 -30
- package/src/LinearSNPCoverageDisplay/models/model.ts +25 -25
- package/src/PileupRenderer/PileupRenderer.tsx +399 -198
- package/src/PileupRenderer/components/PileupRendering.tsx +11 -11
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +5 -0
- package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +7 -5
- package/dist/AlignmentsFeatureDetail/index.test.js +0 -60
- package/dist/BamAdapter/BamAdapter.test.js +0 -177
- package/dist/BamAdapter/MismatchParser.test.js +0 -251
- package/dist/CramAdapter/CramAdapter.test.js +0 -138
- package/dist/LinearAlignmentsDisplay/models/configSchema.test.js +0 -83
- package/dist/LinearPileupDisplay/configSchema.test.js +0 -92
- package/dist/LinearSNPCoverageDisplay/models/configSchema.test.js +0 -62
- package/dist/PileupRenderer/components/PileupRendering.test.js +0 -36
- package/dist/declare.d.js +0 -1
- package/dist/index.test.js +0 -26
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
import BoxRendererType from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType';
|
|
2
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui';
|
|
3
|
+
import { bpSpanPx, iterMap, measureText, } from '@jbrowse/core/util';
|
|
4
|
+
import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils';
|
|
5
|
+
import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache';
|
|
6
|
+
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
7
|
+
// locals
|
|
8
|
+
import { parseCigar, getModificationPositions, getNextRefPos, } from '../BamAdapter/MismatchParser';
|
|
9
|
+
import { sortFeature } from './sortUtil';
|
|
10
|
+
import { getTagAlt, orientationTypes, fetchSequence, shouldFetchReferenceSequence, } from '../util';
|
|
11
|
+
import { PileupLayoutSession, } from './PileupLayoutSession';
|
|
12
|
+
function fillRect(ctx, l, t, w, h, cw, color) {
|
|
13
|
+
if (l + w < 0 || l > cw) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
if (color) {
|
|
18
|
+
ctx.fillStyle = color;
|
|
19
|
+
}
|
|
20
|
+
ctx.fillRect(l, t, w, h);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function getColorBaseMap(theme) {
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
const { bases } = theme.palette;
|
|
26
|
+
return {
|
|
27
|
+
A: bases.A.main,
|
|
28
|
+
C: bases.C.main,
|
|
29
|
+
G: bases.G.main,
|
|
30
|
+
T: bases.T.main,
|
|
31
|
+
deletion: '#808080', // gray
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function getContrastBaseMap(theme) {
|
|
35
|
+
return Object.fromEntries(Object.entries(getColorBaseMap(theme)).map(([key, value]) => [
|
|
36
|
+
key,
|
|
37
|
+
theme.palette.getContrastText(value),
|
|
38
|
+
]));
|
|
39
|
+
}
|
|
40
|
+
const alignmentColoring = {
|
|
41
|
+
color_fwd_strand_not_proper: '#ECC8C8',
|
|
42
|
+
color_rev_strand_not_proper: '#BEBED8',
|
|
43
|
+
color_fwd_strand: '#EC8B8B',
|
|
44
|
+
color_rev_strand: '#8F8FD8',
|
|
45
|
+
color_fwd_missing_mate: '#D11919',
|
|
46
|
+
color_rev_missing_mate: '#1919D1',
|
|
47
|
+
color_fwd_diff_chr: '#000',
|
|
48
|
+
color_rev_diff_chr: '#969696',
|
|
49
|
+
color_pair_lr: '#c8c8c8',
|
|
50
|
+
color_pair_rr: 'navy',
|
|
51
|
+
color_pair_rl: 'teal',
|
|
52
|
+
color_pair_ll: 'green',
|
|
53
|
+
color_nostrand: '#c8c8c8',
|
|
54
|
+
color_interchrom: 'orange',
|
|
55
|
+
color_longinsert: 'red',
|
|
56
|
+
color_shortinsert: 'pink',
|
|
57
|
+
};
|
|
58
|
+
function shouldDrawSNPsMuted(type) {
|
|
59
|
+
return ['methylation', 'modifications'].includes(type || '');
|
|
60
|
+
}
|
|
61
|
+
function shouldDrawIndels(type) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
export default class PileupRenderer extends BoxRendererType {
|
|
65
|
+
constructor() {
|
|
66
|
+
super(...arguments);
|
|
67
|
+
this.supportsSVG = true;
|
|
68
|
+
}
|
|
69
|
+
// get width and height of chars the height is an approximation: width
|
|
70
|
+
// letter M is approximately the height
|
|
71
|
+
getCharWidthHeight(ctx) {
|
|
72
|
+
const charWidth = measureText('A');
|
|
73
|
+
const charHeight = measureText('M') - 2;
|
|
74
|
+
return { charWidth, charHeight };
|
|
75
|
+
}
|
|
76
|
+
layoutFeature({ feature, layout, bpPerPx, region, showSoftClip, heightPx, displayMode, }) {
|
|
77
|
+
let expansionBefore = 0;
|
|
78
|
+
let expansionAfter = 0;
|
|
79
|
+
// Expand the start and end of feature when softclipping enabled
|
|
80
|
+
if (showSoftClip) {
|
|
81
|
+
const mismatches = feature.get('mismatches');
|
|
82
|
+
const seq = feature.get('seq');
|
|
83
|
+
if (seq) {
|
|
84
|
+
for (let i = 0; i < mismatches.length; i += 1) {
|
|
85
|
+
const { type, start, cliplen = 0 } = mismatches[i];
|
|
86
|
+
if (type === 'softclip') {
|
|
87
|
+
start === 0
|
|
88
|
+
? (expansionBefore = cliplen)
|
|
89
|
+
: (expansionAfter = cliplen);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const [leftPx, rightPx] = bpSpanPx(feature.get('start') - expansionBefore, feature.get('end') + expansionAfter, region, bpPerPx);
|
|
95
|
+
if (displayMode === 'compact') {
|
|
96
|
+
heightPx /= 3;
|
|
97
|
+
}
|
|
98
|
+
if (feature.get('refName') !== region.refName) {
|
|
99
|
+
throw new Error(`feature ${feature.id()} is not on the current region's reference sequence ${region.refName}`);
|
|
100
|
+
}
|
|
101
|
+
const topPx = layout.addRect(feature.id(), feature.get('start') - expansionBefore, feature.get('end') + expansionAfter, heightPx, feature);
|
|
102
|
+
if (topPx === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
feature,
|
|
107
|
+
leftPx,
|
|
108
|
+
rightPx,
|
|
109
|
+
topPx: displayMode === 'collapse' ? 0 : topPx,
|
|
110
|
+
heightPx,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// expands region for clipping to use. possible improvement: use average read
|
|
114
|
+
// size to set the heuristic maxClippingSize expansion (e.g. short reads
|
|
115
|
+
// don't have to expand a softclipping size a lot, but long reads might)
|
|
116
|
+
getExpandedRegion(region, renderArgs) {
|
|
117
|
+
const { config, showSoftClip } = renderArgs;
|
|
118
|
+
const maxClippingSize = readConfObject(config, 'maxClippingSize');
|
|
119
|
+
const { start, end } = region;
|
|
120
|
+
const len = end - start;
|
|
121
|
+
const bpExpansion = Math.max(len, showSoftClip ? Math.round(maxClippingSize) : 0);
|
|
122
|
+
return {
|
|
123
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
124
|
+
...region,
|
|
125
|
+
start: Math.floor(Math.max(start - bpExpansion, 0)),
|
|
126
|
+
end: Math.ceil(end + bpExpansion),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
colorByOrientation(feature, config) {
|
|
130
|
+
return alignmentColoring[this.getOrientation(feature, config) || 'color_nostrand'];
|
|
131
|
+
}
|
|
132
|
+
getOrientation(feature, config) {
|
|
133
|
+
const orientationType = readConfObject(config, 'orientationType');
|
|
134
|
+
const type = orientationTypes[orientationType];
|
|
135
|
+
const orientation = type[feature.get('pair_orientation')];
|
|
136
|
+
const map = {
|
|
137
|
+
LR: 'color_pair_lr',
|
|
138
|
+
RR: 'color_pair_rr',
|
|
139
|
+
RL: 'color_pair_rl',
|
|
140
|
+
LL: 'color_pair_ll',
|
|
141
|
+
};
|
|
142
|
+
return map[orientation];
|
|
143
|
+
}
|
|
144
|
+
colorByInsertSize(feature, _config) {
|
|
145
|
+
return feature.get('is_paired') &&
|
|
146
|
+
feature.get('seq_id') !== feature.get('next_seq_id')
|
|
147
|
+
? '#555'
|
|
148
|
+
: `hsl(${Math.abs(feature.get('template_length')) / 10},50%,50%)`;
|
|
149
|
+
}
|
|
150
|
+
colorByStranded(feature, _config) {
|
|
151
|
+
const flags = feature.get('flags');
|
|
152
|
+
const strand = feature.get('strand');
|
|
153
|
+
// is paired
|
|
154
|
+
if (flags & 1) {
|
|
155
|
+
const revflag = flags & 64;
|
|
156
|
+
const flipper = revflag ? -1 : 1;
|
|
157
|
+
// proper pairing
|
|
158
|
+
if (flags & 2) {
|
|
159
|
+
return strand * flipper === 1 ? 'color_rev_strand' : 'color_fwd_strand';
|
|
160
|
+
}
|
|
161
|
+
if (feature.get('multi_segment_next_segment_unmapped')) {
|
|
162
|
+
return strand * flipper === 1
|
|
163
|
+
? 'color_rev_missing_mate'
|
|
164
|
+
: 'color_fwd_missing_mate';
|
|
165
|
+
}
|
|
166
|
+
if (feature.get('seq_id') === feature.get('next_seq_id')) {
|
|
167
|
+
return strand * flipper === 1
|
|
168
|
+
? 'color_rev_strand_not_proper'
|
|
169
|
+
: 'color_fwd_strand_not_proper';
|
|
170
|
+
}
|
|
171
|
+
// should only leave aberrant chr
|
|
172
|
+
return strand === 1 ? 'color_fwd_diff_chr' : 'color_rev_diff_chr';
|
|
173
|
+
}
|
|
174
|
+
return strand === 1 ? 'color_fwd_strand' : 'color_rev_strand';
|
|
175
|
+
}
|
|
176
|
+
colorByPerBaseLettering({ ctx, feat, region, bpPerPx, colorForBase, contrastForBase, charWidth, charHeight, canvasWidth, }) {
|
|
177
|
+
const heightLim = charHeight - 2;
|
|
178
|
+
const { feature, topPx, heightPx } = feat;
|
|
179
|
+
const seq = feature.get('seq');
|
|
180
|
+
const cigarOps = parseCigar(feature.get('CIGAR'));
|
|
181
|
+
const w = 1 / bpPerPx;
|
|
182
|
+
const start = feature.get('start');
|
|
183
|
+
let soffset = 0; // sequence offset
|
|
184
|
+
let roffset = 0; // reference offset
|
|
185
|
+
for (let i = 0; i < cigarOps.length; i += 2) {
|
|
186
|
+
const len = +cigarOps[i];
|
|
187
|
+
const op = cigarOps[i + 1];
|
|
188
|
+
if (op === 'S' || op === 'I') {
|
|
189
|
+
soffset += len;
|
|
190
|
+
}
|
|
191
|
+
else if (op === 'D' || op === 'N') {
|
|
192
|
+
roffset += len;
|
|
193
|
+
}
|
|
194
|
+
else if (op === 'M' || op === 'X' || op === '=') {
|
|
195
|
+
for (let m = 0; m < len; m++) {
|
|
196
|
+
const letter = seq[soffset + m];
|
|
197
|
+
const r = start + roffset + m;
|
|
198
|
+
const [leftPx] = bpSpanPx(r, r + 1, region, bpPerPx);
|
|
199
|
+
fillRect(ctx, leftPx, topPx, w + 0.5, heightPx, canvasWidth, colorForBase[letter]);
|
|
200
|
+
if (w >= charWidth && heightPx >= heightLim) {
|
|
201
|
+
// normal SNP coloring
|
|
202
|
+
ctx.fillStyle = contrastForBase[letter];
|
|
203
|
+
ctx.fillText(letter, leftPx + (w - charWidth) / 2 + 1, topPx + heightPx);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
soffset += len;
|
|
207
|
+
roffset += len;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
colorByPerBaseQuality({ ctx, feat, region, bpPerPx, canvasWidth, }) {
|
|
212
|
+
const { feature, topPx, heightPx } = feat;
|
|
213
|
+
const qual = feature.get('qual') || '';
|
|
214
|
+
const scores = qual.split(' ').map(val => +val);
|
|
215
|
+
const cigarOps = parseCigar(feature.get('CIGAR'));
|
|
216
|
+
const width = 1 / bpPerPx;
|
|
217
|
+
const start = feature.get('start');
|
|
218
|
+
let soffset = 0; // sequence offset
|
|
219
|
+
let roffset = 0; // reference offset
|
|
220
|
+
for (let i = 0; i < cigarOps.length; i += 2) {
|
|
221
|
+
const len = +cigarOps[i];
|
|
222
|
+
const op = cigarOps[i + 1];
|
|
223
|
+
if (op === 'S' || op === 'I') {
|
|
224
|
+
soffset += len;
|
|
225
|
+
}
|
|
226
|
+
else if (op === 'D' || op === 'N') {
|
|
227
|
+
roffset += len;
|
|
228
|
+
}
|
|
229
|
+
else if (op === 'M' || op === 'X' || op === '=') {
|
|
230
|
+
for (let m = 0; m < len; m++) {
|
|
231
|
+
const score = scores[soffset + m];
|
|
232
|
+
const [leftPx] = bpSpanPx(start + roffset + m, start + roffset + m + 1, region, bpPerPx);
|
|
233
|
+
fillRect(ctx, leftPx, topPx, width + 0.5, heightPx, canvasWidth, `hsl(${score === 255 ? 150 : score * 1.5},55%,50%)`);
|
|
234
|
+
}
|
|
235
|
+
soffset += len;
|
|
236
|
+
roffset += len;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// ML stores probabilities as array of numerics and MP is scaled phred scores
|
|
241
|
+
// https://github.com/samtools/hts-specs/pull/418/files#diff-e765c6479316309f56b636f88189cdde8c40b854c7bdcce9ee7fe87a4e76febcR596
|
|
242
|
+
//
|
|
243
|
+
// if we have ML or Ml, it is an 8bit probability, divide by 255
|
|
244
|
+
//
|
|
245
|
+
// if we have MP or Mp it is phred scaled ASCII, which can go up to 90 but
|
|
246
|
+
// has very high likelihood basecalls at that point, we really only care
|
|
247
|
+
// about low qual calls <20 approx
|
|
248
|
+
//
|
|
249
|
+
colorByModifications({ ctx, feat, region, bpPerPx, renderArgs, canvasWidth, }) {
|
|
250
|
+
const { feature, topPx, heightPx } = feat;
|
|
251
|
+
const { Color, modificationTagMap = {} } = renderArgs;
|
|
252
|
+
const mm = getTagAlt(feature, 'MM', 'Mm') || '';
|
|
253
|
+
const ml = getTagAlt(feature, 'ML', 'Ml') || [];
|
|
254
|
+
const probabilities = ml
|
|
255
|
+
? (typeof ml === 'string' ? ml.split(',').map(e => +e) : ml).map(e => e / 255)
|
|
256
|
+
: getTagAlt(feature, 'MP', 'Mp')
|
|
257
|
+
.split('')
|
|
258
|
+
.map(s => s.charCodeAt(0) - 33)
|
|
259
|
+
.map(elt => Math.min(1, elt / 50));
|
|
260
|
+
const cigar = feature.get('CIGAR');
|
|
261
|
+
const start = feature.get('start');
|
|
262
|
+
const seq = feature.get('seq');
|
|
263
|
+
const strand = feature.get('strand');
|
|
264
|
+
const cigarOps = parseCigar(cigar);
|
|
265
|
+
const modifications = getModificationPositions(mm, seq, strand);
|
|
266
|
+
// probIndex applies across multiple modifications e.g.
|
|
267
|
+
let probIndex = 0;
|
|
268
|
+
for (let i = 0; i < modifications.length; i++) {
|
|
269
|
+
const { type, positions } = modifications[i];
|
|
270
|
+
const col = modificationTagMap[type] || 'black';
|
|
271
|
+
// @ts-ignore
|
|
272
|
+
const base = Color(col);
|
|
273
|
+
for (const readPos of getNextRefPos(cigarOps, positions)) {
|
|
274
|
+
const r = start + readPos;
|
|
275
|
+
const [leftPx, rightPx] = bpSpanPx(r, r + 1, region, bpPerPx);
|
|
276
|
+
// give it a little boost of 0.1 to not make them fully
|
|
277
|
+
// invisible to avoid confusion
|
|
278
|
+
const prob = probabilities[probIndex];
|
|
279
|
+
fillRect(ctx, leftPx, topPx, rightPx - leftPx + 0.5, heightPx, canvasWidth, prob && prob !== 1
|
|
280
|
+
? base
|
|
281
|
+
.alpha(prob + 0.1)
|
|
282
|
+
.hsl()
|
|
283
|
+
.string()
|
|
284
|
+
: col);
|
|
285
|
+
probIndex++;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Color by methylation is slightly modified version of color by
|
|
290
|
+
// modifications that focuses on CpG sites, with non-methylated CpG colored
|
|
291
|
+
// blue
|
|
292
|
+
colorByMethylation({ ctx, feat, region, bpPerPx, renderArgs, canvasWidth, }) {
|
|
293
|
+
const { regionSequence } = renderArgs;
|
|
294
|
+
const { feature, topPx, heightPx } = feat;
|
|
295
|
+
const mm = getTagAlt(feature, 'MM', 'Mm') || '';
|
|
296
|
+
if (!regionSequence) {
|
|
297
|
+
throw new Error('region sequence required for methylation');
|
|
298
|
+
}
|
|
299
|
+
const cigar = feature.get('CIGAR');
|
|
300
|
+
const fstart = feature.get('start');
|
|
301
|
+
const fend = feature.get('end');
|
|
302
|
+
const seq = feature.get('seq');
|
|
303
|
+
const strand = feature.get('strand');
|
|
304
|
+
const cigarOps = parseCigar(cigar);
|
|
305
|
+
const methBins = new Array(region.end - region.start).fill(0);
|
|
306
|
+
const modifications = getModificationPositions(mm, seq, strand);
|
|
307
|
+
for (let i = 0; i < modifications.length; i++) {
|
|
308
|
+
const { type, positions } = modifications[i];
|
|
309
|
+
if (type === 'm' && positions) {
|
|
310
|
+
for (const pos of getNextRefPos(cigarOps, positions)) {
|
|
311
|
+
const epos = pos + fstart - region.start;
|
|
312
|
+
if (epos >= 0 && epos < methBins.length) {
|
|
313
|
+
methBins[epos] = 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (let j = fstart; j < fend; j++) {
|
|
319
|
+
const i = j - region.start;
|
|
320
|
+
if (i >= 0 && i < methBins.length) {
|
|
321
|
+
const l1 = regionSequence[i].toLowerCase();
|
|
322
|
+
const l2 = regionSequence[i + 1].toLowerCase();
|
|
323
|
+
// if we are zoomed out, display just a block over the cpg
|
|
324
|
+
if (bpPerPx > 2) {
|
|
325
|
+
if (l1 === 'c' && l2 === 'g') {
|
|
326
|
+
const s = region.start + i;
|
|
327
|
+
const [leftPx, rightPx] = bpSpanPx(s, s + 2, region, bpPerPx);
|
|
328
|
+
fillRect(ctx, leftPx, topPx, rightPx - leftPx + 0.5, heightPx, canvasWidth, methBins[i] || methBins[i + 1] ? 'red' : 'blue');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// if we are zoomed in, color the c inside the cpg
|
|
332
|
+
else {
|
|
333
|
+
// color
|
|
334
|
+
if (l1 === 'c' && l2 === 'g') {
|
|
335
|
+
const s = region.start + i;
|
|
336
|
+
const [leftPx, rightPx] = bpSpanPx(s, s + 1, region, bpPerPx);
|
|
337
|
+
fillRect(ctx, leftPx, topPx, rightPx - leftPx + 0.5, heightPx, canvasWidth, methBins[i] ? 'red' : 'blue');
|
|
338
|
+
const [leftPx2, rightPx2] = bpSpanPx(s + 1, s + 2, region, bpPerPx);
|
|
339
|
+
fillRect(ctx, leftPx2, topPx, rightPx2 - leftPx2 + 0.5, heightPx, canvasWidth, methBins[i + 1] ? 'red' : 'blue');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
drawRect(ctx, feat, props) {
|
|
346
|
+
const { regions, bpPerPx } = props;
|
|
347
|
+
const { heightPx, topPx, feature } = feat;
|
|
348
|
+
const [region] = regions;
|
|
349
|
+
const [leftPx, rightPx] = bpSpanPx(feature.get('start'), feature.get('end'), region, bpPerPx);
|
|
350
|
+
const flip = region.reversed ? -1 : 1;
|
|
351
|
+
const strand = feature.get('strand') * flip;
|
|
352
|
+
if (bpPerPx < 10) {
|
|
353
|
+
if (strand === -1) {
|
|
354
|
+
ctx.beginPath();
|
|
355
|
+
ctx.moveTo(leftPx - 5, topPx + heightPx / 2);
|
|
356
|
+
ctx.lineTo(leftPx, topPx + heightPx);
|
|
357
|
+
ctx.lineTo(rightPx, topPx + heightPx);
|
|
358
|
+
ctx.lineTo(rightPx, topPx);
|
|
359
|
+
ctx.lineTo(leftPx, topPx);
|
|
360
|
+
ctx.closePath();
|
|
361
|
+
ctx.fill();
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
ctx.beginPath();
|
|
365
|
+
ctx.moveTo(leftPx, topPx);
|
|
366
|
+
ctx.lineTo(leftPx, topPx + heightPx);
|
|
367
|
+
ctx.lineTo(rightPx, topPx + heightPx);
|
|
368
|
+
ctx.lineTo(rightPx + 5, topPx + heightPx / 2);
|
|
369
|
+
ctx.lineTo(rightPx, topPx);
|
|
370
|
+
ctx.closePath();
|
|
371
|
+
ctx.fill();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
ctx.fillRect(leftPx, topPx, rightPx - leftPx, heightPx);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
drawAlignmentRect({ ctx, feat, renderArgs, colorForBase, contrastForBase, charWidth, charHeight, defaultColor, canvasWidth, }) {
|
|
379
|
+
const { config, bpPerPx, regions, colorBy, colorTagMap = {} } = renderArgs;
|
|
380
|
+
const { tag = '', type: colorType = '' } = colorBy || {};
|
|
381
|
+
const { feature } = feat;
|
|
382
|
+
const region = regions[0];
|
|
383
|
+
// first pass for simple color changes that change the color of the
|
|
384
|
+
// alignment
|
|
385
|
+
switch (colorType) {
|
|
386
|
+
case 'insertSize':
|
|
387
|
+
ctx.fillStyle = this.colorByInsertSize(feature, config);
|
|
388
|
+
break;
|
|
389
|
+
case 'strand':
|
|
390
|
+
ctx.fillStyle = feature.get('strand') === -1 ? '#8F8FD8' : '#EC8B8B';
|
|
391
|
+
break;
|
|
392
|
+
case 'mappingQuality':
|
|
393
|
+
ctx.fillStyle = `hsl(${feature.get('mq')},50%,50%)`;
|
|
394
|
+
break;
|
|
395
|
+
case 'pairOrientation':
|
|
396
|
+
ctx.fillStyle = this.colorByOrientation(feature, config);
|
|
397
|
+
break;
|
|
398
|
+
case 'stranded':
|
|
399
|
+
ctx.fillStyle = alignmentColoring[this.colorByStranded(feature, config)];
|
|
400
|
+
break;
|
|
401
|
+
case 'xs':
|
|
402
|
+
case 'tag': {
|
|
403
|
+
const tags = feature.get('tags');
|
|
404
|
+
const val = tags ? tags[tag] : feature.get(tag);
|
|
405
|
+
// special for for XS/TS tag
|
|
406
|
+
if (tag === 'XS' || tag === 'TS') {
|
|
407
|
+
const map = {
|
|
408
|
+
'-': 'color_rev_strand',
|
|
409
|
+
'+': 'color_fwd_strand',
|
|
410
|
+
};
|
|
411
|
+
ctx.fillStyle = alignmentColoring[map[val] || 'color_nostrand'];
|
|
412
|
+
}
|
|
413
|
+
// lower case 'ts' from minimap2 is flipped from xs
|
|
414
|
+
if (tag === 'ts') {
|
|
415
|
+
const map = {
|
|
416
|
+
'-': feature.get('strand') === -1
|
|
417
|
+
? 'color_fwd_strand'
|
|
418
|
+
: 'color_rev_strand',
|
|
419
|
+
'+': feature.get('strand') === -1
|
|
420
|
+
? 'color_rev_strand'
|
|
421
|
+
: 'color_fwd_strand',
|
|
422
|
+
};
|
|
423
|
+
ctx.fillStyle = alignmentColoring[map[val] || 'color_nostrand'];
|
|
424
|
+
}
|
|
425
|
+
// tag is not one of the autofilled tags, has color-value pairs from
|
|
426
|
+
// fetchValues
|
|
427
|
+
else {
|
|
428
|
+
const foundValue = colorTagMap[val];
|
|
429
|
+
ctx.fillStyle = foundValue || alignmentColoring['color_nostrand'];
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case 'insertSizeAndPairOrientation':
|
|
434
|
+
break;
|
|
435
|
+
case 'modifications':
|
|
436
|
+
case 'methylation':
|
|
437
|
+
// this coloring is similar to igv.js, and is helpful to color negative
|
|
438
|
+
// strand reads differently because their c-g will be flipped (e.g. g-c
|
|
439
|
+
// read right to left)
|
|
440
|
+
if (feature.get('flags') & 16) {
|
|
441
|
+
ctx.fillStyle = '#c8dcc8';
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
ctx.fillStyle = '#c8c8c8';
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
case 'normal':
|
|
448
|
+
default:
|
|
449
|
+
if (defaultColor) {
|
|
450
|
+
// avoid a readConfObject call here
|
|
451
|
+
ctx.fillStyle = '#c8c8c8';
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
ctx.fillStyle = readConfObject(config, 'color', { feature });
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
this.drawRect(ctx, feat, renderArgs);
|
|
459
|
+
// second pass for color types that render per-base things that go over the
|
|
460
|
+
// existing drawing
|
|
461
|
+
switch (colorType) {
|
|
462
|
+
case 'perBaseQuality':
|
|
463
|
+
this.colorByPerBaseQuality({
|
|
464
|
+
ctx,
|
|
465
|
+
feat,
|
|
466
|
+
region,
|
|
467
|
+
bpPerPx,
|
|
468
|
+
canvasWidth,
|
|
469
|
+
});
|
|
470
|
+
break;
|
|
471
|
+
case 'perBaseLettering':
|
|
472
|
+
this.colorByPerBaseLettering({
|
|
473
|
+
ctx,
|
|
474
|
+
feat,
|
|
475
|
+
region,
|
|
476
|
+
bpPerPx,
|
|
477
|
+
colorForBase,
|
|
478
|
+
contrastForBase,
|
|
479
|
+
charWidth,
|
|
480
|
+
charHeight,
|
|
481
|
+
canvasWidth,
|
|
482
|
+
});
|
|
483
|
+
break;
|
|
484
|
+
case 'modifications':
|
|
485
|
+
this.colorByModifications({
|
|
486
|
+
ctx,
|
|
487
|
+
feat,
|
|
488
|
+
region,
|
|
489
|
+
bpPerPx,
|
|
490
|
+
renderArgs,
|
|
491
|
+
canvasWidth,
|
|
492
|
+
});
|
|
493
|
+
break;
|
|
494
|
+
case 'methylation':
|
|
495
|
+
this.colorByMethylation({
|
|
496
|
+
ctx,
|
|
497
|
+
feat,
|
|
498
|
+
region,
|
|
499
|
+
bpPerPx,
|
|
500
|
+
renderArgs,
|
|
501
|
+
canvasWidth,
|
|
502
|
+
});
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
drawMismatches({ ctx, feat, renderArgs, minSubfeatureWidth, largeInsertionIndicatorScale, mismatchAlpha, charWidth, charHeight, colorForBase, contrastForBase, canvasWidth, drawSNPsMuted, drawIndels = true, }) {
|
|
507
|
+
const { Color, bpPerPx, regions } = renderArgs;
|
|
508
|
+
const { heightPx, topPx, feature } = feat;
|
|
509
|
+
const [region] = regions;
|
|
510
|
+
const start = feature.get('start');
|
|
511
|
+
const pxPerBp = Math.min(1 / bpPerPx, 2);
|
|
512
|
+
const w = Math.max(minSubfeatureWidth, pxPerBp);
|
|
513
|
+
const mismatches = feature.get('mismatches');
|
|
514
|
+
const heightLim = charHeight - 2;
|
|
515
|
+
// extraHorizontallyFlippedOffset is used to draw interbase items, which
|
|
516
|
+
// are located to the left when forward and right when reversed
|
|
517
|
+
const extraHorizontallyFlippedOffset = region.reversed
|
|
518
|
+
? 1 / bpPerPx + 1
|
|
519
|
+
: -1;
|
|
520
|
+
// two pass rendering: first pass, draw all the mismatches except wide
|
|
521
|
+
// insertion markers
|
|
522
|
+
for (let i = 0; i < mismatches.length; i += 1) {
|
|
523
|
+
const mismatch = mismatches[i];
|
|
524
|
+
const mstart = start + mismatch.start;
|
|
525
|
+
const mlen = mismatch.length;
|
|
526
|
+
const mbase = mismatch.base;
|
|
527
|
+
const [leftPx, rightPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx);
|
|
528
|
+
const widthPx = Math.max(minSubfeatureWidth, Math.abs(leftPx - rightPx));
|
|
529
|
+
if (mismatch.type === 'mismatch') {
|
|
530
|
+
if (!drawSNPsMuted) {
|
|
531
|
+
const baseColor = colorForBase[mismatch.base] || '#888';
|
|
532
|
+
fillRect(ctx, leftPx, topPx, widthPx, heightPx, canvasWidth, !mismatchAlpha
|
|
533
|
+
? baseColor
|
|
534
|
+
: mismatch.qual !== undefined
|
|
535
|
+
? // @ts-ignore
|
|
536
|
+
Color(baseColor)
|
|
537
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
538
|
+
.hsl()
|
|
539
|
+
.string()
|
|
540
|
+
: baseColor);
|
|
541
|
+
}
|
|
542
|
+
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
543
|
+
// normal SNP coloring
|
|
544
|
+
const contrastColor = drawSNPsMuted
|
|
545
|
+
? 'black'
|
|
546
|
+
: contrastForBase[mismatch.base] || 'black';
|
|
547
|
+
ctx.fillStyle = !mismatchAlpha
|
|
548
|
+
? contrastColor
|
|
549
|
+
: mismatch.qual !== undefined
|
|
550
|
+
? // @ts-ignore
|
|
551
|
+
Color(contrastColor)
|
|
552
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
553
|
+
.hsl()
|
|
554
|
+
.string()
|
|
555
|
+
: contrastColor;
|
|
556
|
+
ctx.fillText(mbase, leftPx + (widthPx - charWidth) / 2 + 1, topPx + heightPx);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else if (mismatch.type === 'deletion' && drawIndels) {
|
|
560
|
+
fillRect(ctx, leftPx, topPx, widthPx, heightPx, canvasWidth, colorForBase.deletion);
|
|
561
|
+
const txt = `${mismatch.length}`;
|
|
562
|
+
const rwidth = measureText(txt, 10);
|
|
563
|
+
if (widthPx >= rwidth && heightPx >= heightLim) {
|
|
564
|
+
ctx.fillStyle = contrastForBase.deletion;
|
|
565
|
+
ctx.fillText(txt, (leftPx + rightPx) / 2 - rwidth / 2, topPx + heightPx);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else if (mismatch.type === 'insertion' && drawIndels) {
|
|
569
|
+
ctx.fillStyle = 'purple';
|
|
570
|
+
const pos = leftPx + extraHorizontallyFlippedOffset;
|
|
571
|
+
const len = +mismatch.base || mismatch.length;
|
|
572
|
+
const insW = Math.max(minSubfeatureWidth / 2, Math.min(1.2, 1 / bpPerPx));
|
|
573
|
+
if (len < 10) {
|
|
574
|
+
fillRect(ctx, pos, topPx, insW, heightPx, canvasWidth, 'purple');
|
|
575
|
+
if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
|
|
576
|
+
fillRect(ctx, pos - insW, topPx, insW * 3, 1, canvasWidth);
|
|
577
|
+
fillRect(ctx, pos - insW, topPx + heightPx - 1, insW * 3, 1, canvasWidth);
|
|
578
|
+
ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
|
|
583
|
+
const pos = leftPx + extraHorizontallyFlippedOffset;
|
|
584
|
+
fillRect(ctx, pos, topPx, w, heightPx, canvasWidth, mismatch.type === 'hardclip' ? 'red' : 'blue');
|
|
585
|
+
if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
|
|
586
|
+
fillRect(ctx, pos - w, topPx, w * 3, 1, canvasWidth);
|
|
587
|
+
fillRect(ctx, pos - w, topPx + heightPx - 1, w * 3, 1, canvasWidth);
|
|
588
|
+
ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else if (mismatch.type === 'skip') {
|
|
592
|
+
// fix to avoid bad rendering note that this was also related to chrome
|
|
593
|
+
// bug https://bugs.chromium.org/p/chromium/issues/detail?id=1131528
|
|
594
|
+
// also affected firefox ref #1236 #2750
|
|
595
|
+
if (leftPx + widthPx > 0) {
|
|
596
|
+
// make small exons more visible when zoomed far out
|
|
597
|
+
const adjustPx = widthPx - (bpPerPx > 10 ? 1.5 : 0);
|
|
598
|
+
ctx.clearRect(leftPx, topPx, adjustPx, heightPx);
|
|
599
|
+
fillRect(ctx, Math.max(0, leftPx), topPx + heightPx / 2 - 1, adjustPx + (leftPx < 0 ? leftPx : 0), 2, canvasWidth, '#333');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// second pass, draw wide insertion markers on top
|
|
604
|
+
if (drawIndels) {
|
|
605
|
+
for (let i = 0; i < mismatches.length; i += 1) {
|
|
606
|
+
const mismatch = mismatches[i];
|
|
607
|
+
const mstart = start + mismatch.start;
|
|
608
|
+
const mlen = mismatch.length;
|
|
609
|
+
const [leftPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx);
|
|
610
|
+
const len = +mismatch.base || mismatch.length;
|
|
611
|
+
const txt = `${len}`;
|
|
612
|
+
if (mismatch.type === 'insertion' && len >= 10) {
|
|
613
|
+
if (bpPerPx > largeInsertionIndicatorScale) {
|
|
614
|
+
fillRect(ctx, leftPx - 1, topPx, 2, heightPx, canvasWidth, 'purple');
|
|
615
|
+
}
|
|
616
|
+
else if (heightPx > charHeight) {
|
|
617
|
+
const rwidth = measureText(txt);
|
|
618
|
+
const padding = 5;
|
|
619
|
+
fillRect(ctx, leftPx - rwidth / 2 - padding, topPx, rwidth + 2 * padding, heightPx, canvasWidth, 'purple');
|
|
620
|
+
ctx.fillStyle = 'white';
|
|
621
|
+
ctx.fillText(txt, leftPx - rwidth / 2, topPx + heightPx);
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
const padding = 2;
|
|
625
|
+
fillRect(ctx, leftPx - padding, topPx, 2 * padding, heightPx, canvasWidth, 'purple');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
drawSoftClipping({ ctx, feat, renderArgs, config, theme, canvasWidth, }) {
|
|
632
|
+
const { feature, topPx, heightPx } = feat;
|
|
633
|
+
const { regions, bpPerPx } = renderArgs;
|
|
634
|
+
const [region] = regions;
|
|
635
|
+
const minFeatWidth = readConfObject(config, 'minSubfeatureWidth');
|
|
636
|
+
const mismatches = feature.get('mismatches');
|
|
637
|
+
const seq = feature.get('seq');
|
|
638
|
+
const { charWidth, charHeight } = this.getCharWidthHeight(ctx);
|
|
639
|
+
// @ts-ignore
|
|
640
|
+
const { bases } = theme.palette;
|
|
641
|
+
const colorForBase = {
|
|
642
|
+
A: bases.A.main,
|
|
643
|
+
C: bases.C.main,
|
|
644
|
+
G: bases.G.main,
|
|
645
|
+
T: bases.T.main,
|
|
646
|
+
deletion: '#808080', // gray
|
|
647
|
+
};
|
|
648
|
+
// Display all bases softclipped off in lightened colors
|
|
649
|
+
if (seq) {
|
|
650
|
+
mismatches
|
|
651
|
+
.filter(mismatch => mismatch.type === 'softclip')
|
|
652
|
+
.forEach(mismatch => {
|
|
653
|
+
const softClipLength = mismatch.cliplen || 0;
|
|
654
|
+
const s = feature.get('start');
|
|
655
|
+
const softClipStart = mismatch.start === 0 ? s - softClipLength : s + mismatch.start;
|
|
656
|
+
for (let k = 0; k < softClipLength; k += 1) {
|
|
657
|
+
const base = seq.charAt(k + mismatch.start);
|
|
658
|
+
// If softclip length+start is longer than sequence, no need to
|
|
659
|
+
// continue showing base
|
|
660
|
+
if (!base) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const [softClipLeftPx, softClipRightPx] = bpSpanPx(softClipStart + k, softClipStart + k + 1, region, bpPerPx);
|
|
664
|
+
const softClipWidthPx = Math.max(minFeatWidth, Math.abs(softClipLeftPx - softClipRightPx));
|
|
665
|
+
// Black accounts for IUPAC ambiguity code bases such as N that
|
|
666
|
+
// show in soft clipping
|
|
667
|
+
const baseColor = colorForBase[base] || '#000000';
|
|
668
|
+
ctx.fillStyle = baseColor;
|
|
669
|
+
fillRect(ctx, softClipLeftPx, topPx, softClipWidthPx, heightPx, canvasWidth);
|
|
670
|
+
if (softClipWidthPx >= charWidth && heightPx >= charHeight - 5) {
|
|
671
|
+
ctx.fillStyle = theme.palette.getContrastText(baseColor);
|
|
672
|
+
ctx.fillText(base, softClipLeftPx + (softClipWidthPx - charWidth) / 2 + 1, topPx + heightPx);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
makeImageData({ ctx, layoutRecords, canvasWidth, renderArgs, }) {
|
|
679
|
+
const { layout, config, showSoftClip, colorBy, theme: configTheme, } = renderArgs;
|
|
680
|
+
const mismatchAlpha = readConfObject(config, 'mismatchAlpha');
|
|
681
|
+
const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth');
|
|
682
|
+
const largeInsertionIndicatorScale = readConfObject(config, 'largeInsertionIndicatorScale');
|
|
683
|
+
const defaultColor = readConfObject(config, 'color') === '#f0f';
|
|
684
|
+
const theme = createJBrowseTheme(configTheme);
|
|
685
|
+
const colorForBase = getColorBaseMap(theme);
|
|
686
|
+
const contrastForBase = getContrastBaseMap(theme);
|
|
687
|
+
if (!layout) {
|
|
688
|
+
throw new Error(`layout required`);
|
|
689
|
+
}
|
|
690
|
+
if (!layout.addRect) {
|
|
691
|
+
throw new Error('invalid layout object');
|
|
692
|
+
}
|
|
693
|
+
ctx.font = 'bold 10px Courier New,monospace';
|
|
694
|
+
const { charWidth, charHeight } = this.getCharWidthHeight(ctx);
|
|
695
|
+
const drawSNPsMuted = shouldDrawSNPsMuted(colorBy === null || colorBy === void 0 ? void 0 : colorBy.type);
|
|
696
|
+
const drawIndels = shouldDrawIndels(colorBy === null || colorBy === void 0 ? void 0 : colorBy.type);
|
|
697
|
+
for (let i = 0; i < layoutRecords.length; i++) {
|
|
698
|
+
const feat = layoutRecords[i];
|
|
699
|
+
if (feat === null) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
this.drawAlignmentRect({
|
|
703
|
+
ctx,
|
|
704
|
+
feat,
|
|
705
|
+
renderArgs,
|
|
706
|
+
defaultColor,
|
|
707
|
+
colorForBase,
|
|
708
|
+
contrastForBase,
|
|
709
|
+
charWidth,
|
|
710
|
+
charHeight,
|
|
711
|
+
canvasWidth,
|
|
712
|
+
});
|
|
713
|
+
this.drawMismatches({
|
|
714
|
+
ctx,
|
|
715
|
+
feat,
|
|
716
|
+
renderArgs,
|
|
717
|
+
mismatchAlpha,
|
|
718
|
+
drawSNPsMuted,
|
|
719
|
+
drawIndels,
|
|
720
|
+
largeInsertionIndicatorScale,
|
|
721
|
+
minSubfeatureWidth,
|
|
722
|
+
charWidth,
|
|
723
|
+
charHeight,
|
|
724
|
+
colorForBase,
|
|
725
|
+
contrastForBase,
|
|
726
|
+
canvasWidth,
|
|
727
|
+
});
|
|
728
|
+
if (showSoftClip) {
|
|
729
|
+
this.drawSoftClipping({
|
|
730
|
+
ctx,
|
|
731
|
+
feat,
|
|
732
|
+
renderArgs,
|
|
733
|
+
config,
|
|
734
|
+
theme,
|
|
735
|
+
canvasWidth,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// we perform a full layout before render as a separate method because the
|
|
741
|
+
// layout determines the height of the canvas that we use to render
|
|
742
|
+
layoutFeats(props) {
|
|
743
|
+
const { layout, features, sortedBy, config, bpPerPx, showSoftClip, regions, } = props;
|
|
744
|
+
const [region] = regions;
|
|
745
|
+
if (!layout) {
|
|
746
|
+
throw new Error(`layout required`);
|
|
747
|
+
}
|
|
748
|
+
if (!layout.addRect) {
|
|
749
|
+
throw new Error('invalid layout object');
|
|
750
|
+
}
|
|
751
|
+
const featureMap = (sortedBy === null || sortedBy === void 0 ? void 0 : sortedBy.type) && region.start === sortedBy.pos
|
|
752
|
+
? sortFeature(features, sortedBy)
|
|
753
|
+
: features;
|
|
754
|
+
const heightPx = readConfObject(config, 'height');
|
|
755
|
+
const displayMode = readConfObject(config, 'displayMode');
|
|
756
|
+
return iterMap(featureMap.values(), feature => this.layoutFeature({
|
|
757
|
+
feature,
|
|
758
|
+
layout,
|
|
759
|
+
bpPerPx,
|
|
760
|
+
region,
|
|
761
|
+
showSoftClip,
|
|
762
|
+
heightPx,
|
|
763
|
+
displayMode,
|
|
764
|
+
}), featureMap.size);
|
|
765
|
+
}
|
|
766
|
+
async fetchSequence(renderProps) {
|
|
767
|
+
const { sessionId, regions, adapterConfig } = renderProps;
|
|
768
|
+
const { sequenceAdapter } = adapterConfig;
|
|
769
|
+
if (!sequenceAdapter) {
|
|
770
|
+
return undefined;
|
|
771
|
+
}
|
|
772
|
+
const { dataAdapter } = await getAdapter(this.pluginManager, sessionId, sequenceAdapter);
|
|
773
|
+
const [region] = regions;
|
|
774
|
+
return fetchSequence(region, dataAdapter);
|
|
775
|
+
}
|
|
776
|
+
async render(renderProps) {
|
|
777
|
+
var _a;
|
|
778
|
+
const features = await this.getFeatures(renderProps);
|
|
779
|
+
const layout = this.createLayoutInWorker(renderProps);
|
|
780
|
+
const { regions, bpPerPx } = renderProps;
|
|
781
|
+
const layoutRecords = this.layoutFeats({
|
|
782
|
+
...renderProps,
|
|
783
|
+
features,
|
|
784
|
+
layout,
|
|
785
|
+
});
|
|
786
|
+
const [region] = regions;
|
|
787
|
+
// only need reference sequence if there are features and only for some
|
|
788
|
+
// cases
|
|
789
|
+
const regionSequence = features.size && shouldFetchReferenceSequence((_a = renderProps.colorBy) === null || _a === void 0 ? void 0 : _a.type)
|
|
790
|
+
? await this.fetchSequence(renderProps)
|
|
791
|
+
: undefined;
|
|
792
|
+
const { end, start } = region;
|
|
793
|
+
const width = (end - start) / bpPerPx;
|
|
794
|
+
const height = Math.max(layout.getTotalHeight(), 1);
|
|
795
|
+
const Color = await import('color').then(f => f.default);
|
|
796
|
+
const res = await renderToAbstractCanvas(width, height, renderProps, (ctx) => this.makeImageData({
|
|
797
|
+
ctx,
|
|
798
|
+
layoutRecords,
|
|
799
|
+
canvasWidth: width,
|
|
800
|
+
renderArgs: {
|
|
801
|
+
...renderProps,
|
|
802
|
+
layout,
|
|
803
|
+
features,
|
|
804
|
+
regionSequence,
|
|
805
|
+
Color,
|
|
806
|
+
},
|
|
807
|
+
}));
|
|
808
|
+
const results = await super.render({
|
|
809
|
+
...renderProps,
|
|
810
|
+
...res,
|
|
811
|
+
features,
|
|
812
|
+
layout,
|
|
813
|
+
height,
|
|
814
|
+
width,
|
|
815
|
+
});
|
|
816
|
+
return {
|
|
817
|
+
...results,
|
|
818
|
+
...res,
|
|
819
|
+
features: new Map(),
|
|
820
|
+
layout,
|
|
821
|
+
height,
|
|
822
|
+
width,
|
|
823
|
+
maxHeightReached: layout.maxHeightReached,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
createSession(args) {
|
|
827
|
+
return new PileupLayoutSession(args);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
//# sourceMappingURL=PileupRenderer.js.map
|