@jbrowse/plugin-alignments 1.5.0 → 1.5.4
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/index.d.ts +7 -4
- package/dist/AlignmentsTrack/index.d.ts +2 -0
- package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +1 -1
- package/dist/BamAdapter/index.d.ts +2 -4
- package/dist/CramAdapter/index.d.ts +1 -4
- package/dist/HtsgetBamAdapter/HtsgetBamAdapter.d.ts +2 -1
- package/dist/HtsgetBamAdapter/index.d.ts +2 -4
- package/dist/LinearAlignmentsDisplay/index.d.ts +2 -3
- package/dist/LinearPileupDisplay/index.d.ts +2 -2
- package/dist/LinearPileupDisplay/model.d.ts +1 -2
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
- package/dist/LinearSNPCoverageDisplay/index.d.ts +2 -2
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +1 -0
- package/dist/PileupRenderer/PileupRenderer.d.ts +20 -7
- package/dist/PileupRenderer/index.d.ts +2 -3
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +38 -26
- package/dist/SNPCoverageAdapter/index.d.ts +1 -5
- package/dist/SNPCoverageRenderer/index.d.ts +3 -3
- package/dist/plugin-alignments.cjs.development.js +3696 -3526
- package/dist/plugin-alignments.cjs.development.js.map +1 -1
- package/dist/plugin-alignments.cjs.production.min.js +1 -1
- package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
- package/dist/plugin-alignments.esm.js +3690 -3520
- package/dist/plugin-alignments.esm.js.map +1 -1
- package/package.json +4 -4
- package/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap +8 -0
- package/src/AlignmentsFeatureDetail/{index.js → index.ts} +19 -3
- package/src/AlignmentsTrack/index.ts +36 -0
- package/src/BamAdapter/BamSlightlyLazyFeature.ts +14 -30
- package/src/BamAdapter/MismatchParser.test.ts +20 -0
- package/src/BamAdapter/MismatchParser.ts +6 -5
- package/src/BamAdapter/index.ts +11 -5
- package/src/CramAdapter/CramSlightlyLazyFeature.ts +1 -5
- package/src/CramAdapter/index.ts +11 -4
- package/src/HtsgetBamAdapter/HtsgetBamAdapter.ts +2 -2
- package/src/HtsgetBamAdapter/index.ts +18 -5
- package/src/LinearAlignmentsDisplay/index.ts +20 -3
- package/src/LinearAlignmentsDisplay/models/configSchema.test.js +8 -68
- package/src/LinearPileupDisplay/configSchema.test.js +2 -13
- package/src/LinearPileupDisplay/index.ts +19 -2
- package/src/LinearPileupDisplay/model.ts +15 -20
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +17 -12
- package/src/LinearSNPCoverageDisplay/index.ts +19 -2
- package/src/LinearSNPCoverageDisplay/models/configSchema.test.js +2 -13
- package/src/LinearSNPCoverageDisplay/models/model.ts +21 -0
- package/src/PileupRenderer/PileupRenderer.tsx +154 -128
- package/src/PileupRenderer/components/PileupRendering.tsx +2 -0
- package/src/PileupRenderer/configSchema.ts +2 -2
- package/src/PileupRenderer/index.ts +16 -3
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +95 -25
- package/src/SNPCoverageAdapter/index.ts +17 -5
- package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +60 -13
- package/src/SNPCoverageRenderer/configSchema.js +5 -0
- package/src/SNPCoverageRenderer/index.ts +24 -0
- package/src/index.ts +91 -163
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.test.ts +0 -275
- package/src/SNPCoverageAdapter/__snapshots__/SNPCoverageAdapter.test.ts.snap +0 -579
- package/src/SNPCoverageRenderer/index.js +0 -11
|
@@ -11,6 +11,15 @@ type Count = {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
type SNPInfo = {
|
|
15
|
+
ref: Count
|
|
16
|
+
cov: Count
|
|
17
|
+
lowqual: Count
|
|
18
|
+
noncov: Count
|
|
19
|
+
delskips: Count
|
|
20
|
+
total: number
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
const en = (n: number) => n.toLocaleString('en-US')
|
|
15
24
|
|
|
16
25
|
const TooltipContents = React.forwardRef(
|
|
@@ -22,16 +31,8 @@ const TooltipContents = React.forwardRef(
|
|
|
22
31
|
.filter(f => !!f)
|
|
23
32
|
.join(':')
|
|
24
33
|
|
|
25
|
-
const info = feature.get('snpinfo') as
|
|
26
|
-
|
|
27
|
-
cov: Count
|
|
28
|
-
lowqual: Count
|
|
29
|
-
noncov: Count
|
|
30
|
-
delskips: Count
|
|
31
|
-
total: number
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const total = info.total
|
|
34
|
+
const info = feature.get('snpinfo') as SNPInfo
|
|
35
|
+
const total = info?.total
|
|
35
36
|
|
|
36
37
|
return (
|
|
37
38
|
<div ref={ref}>
|
|
@@ -61,7 +62,7 @@ const TooltipContents = React.forwardRef(
|
|
|
61
62
|
<td>{base.toUpperCase()}</td>
|
|
62
63
|
<td>{score.total}</td>
|
|
63
64
|
<td>
|
|
64
|
-
{base === 'total'
|
|
65
|
+
{base === 'total' || base === 'skip'
|
|
65
66
|
? '---'
|
|
66
67
|
: `${Math.floor((score.total / total) * 100)}%`}
|
|
67
68
|
</td>
|
|
@@ -91,7 +92,11 @@ const SNPCoverageTooltip = observer(
|
|
|
91
92
|
clientMouseCoord: Coord
|
|
92
93
|
clientRect?: ClientRect
|
|
93
94
|
}) => {
|
|
94
|
-
|
|
95
|
+
const { model } = props
|
|
96
|
+
const { featureUnderMouse: feat } = model
|
|
97
|
+
return feat && feat.get('type') === 'skip' ? null : (
|
|
98
|
+
<Tooltip TooltipContents={TooltipContents} {...props} />
|
|
99
|
+
)
|
|
95
100
|
},
|
|
96
101
|
)
|
|
97
102
|
|
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import configSchemaFactory from './models/configSchema'
|
|
3
|
+
import modelFactory from './models/model'
|
|
4
|
+
import { LinearWiggleDisplayReactComponent } from '@jbrowse/plugin-wiggle'
|
|
5
|
+
import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
|
|
6
|
+
|
|
7
|
+
export default function register(pluginManager: PluginManager) {
|
|
8
|
+
pluginManager.addDisplayType(() => {
|
|
9
|
+
const configSchema = configSchemaFactory(pluginManager)
|
|
10
|
+
return new DisplayType({
|
|
11
|
+
name: 'LinearSNPCoverageDisplay',
|
|
12
|
+
configSchema,
|
|
13
|
+
stateModel: modelFactory(pluginManager, configSchema),
|
|
14
|
+
trackType: 'AlignmentsTrack',
|
|
15
|
+
viewType: 'LinearGenomeView',
|
|
16
|
+
ReactComponent: LinearWiggleDisplayReactComponent,
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin'
|
|
2
2
|
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
|
-
import SNPCoverageRenderer
|
|
4
|
-
configSchema as snpCoverageRendererConfigSchema,
|
|
5
|
-
ReactComponent as SNPCoverageRendererReactComponent,
|
|
6
|
-
} from '../../SNPCoverageRenderer' // change renderer
|
|
3
|
+
import SNPCoverageRenderer from '../../SNPCoverageRenderer' // change renderer
|
|
7
4
|
import configSchemaFactory from './configSchema'
|
|
8
5
|
|
|
9
6
|
// mock warnings to avoid unnecessary outputs
|
|
@@ -17,15 +14,7 @@ afterEach(() => {
|
|
|
17
14
|
// change renderer
|
|
18
15
|
class SNPCoverageRendererPlugin extends Plugin {
|
|
19
16
|
install(pluginManager) {
|
|
20
|
-
pluginManager
|
|
21
|
-
() =>
|
|
22
|
-
new SNPCoverageRenderer({
|
|
23
|
-
name: 'SNPCoverageRenderer',
|
|
24
|
-
ReactComponent: SNPCoverageRendererReactComponent,
|
|
25
|
-
configSchema: snpCoverageRendererConfigSchema,
|
|
26
|
-
pluginManager,
|
|
27
|
-
}),
|
|
28
|
-
)
|
|
17
|
+
SNPCoverageRenderer(pluginManager)
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
20
|
|
|
@@ -30,6 +30,7 @@ const stateModelFactory = (
|
|
|
30
30
|
type: types.literal('LinearSNPCoverageDisplay'),
|
|
31
31
|
drawInterbaseCounts: types.maybe(types.boolean),
|
|
32
32
|
drawIndicators: types.maybe(types.boolean),
|
|
33
|
+
drawArcs: types.maybe(types.boolean),
|
|
33
34
|
filterBy: types.optional(
|
|
34
35
|
types.model({
|
|
35
36
|
flagInclude: types.optional(types.number, 0),
|
|
@@ -98,10 +99,19 @@ const stateModelFactory = (
|
|
|
98
99
|
self.drawIndicators === undefined
|
|
99
100
|
? configBlob.drawIndicators
|
|
100
101
|
: self.drawIndicators,
|
|
102
|
+
drawArcs:
|
|
103
|
+
self.drawArcs === undefined
|
|
104
|
+
? configBlob.drawArcs
|
|
105
|
+
: self.drawArcs,
|
|
101
106
|
},
|
|
102
107
|
getEnv(self),
|
|
103
108
|
)
|
|
104
109
|
},
|
|
110
|
+
get drawArcsSetting() {
|
|
111
|
+
return self.drawArcs !== undefined
|
|
112
|
+
? self.drawArcs
|
|
113
|
+
: readConfObject(this.rendererConfig, 'drawArcs')
|
|
114
|
+
},
|
|
105
115
|
get drawInterbaseCountsSetting() {
|
|
106
116
|
return self.drawInterbaseCounts !== undefined
|
|
107
117
|
? self.drawInterbaseCounts
|
|
@@ -143,6 +153,9 @@ const stateModelFactory = (
|
|
|
143
153
|
toggleDrawInterbaseCounts() {
|
|
144
154
|
self.drawInterbaseCounts = !self.drawInterbaseCountsSetting
|
|
145
155
|
},
|
|
156
|
+
toggleDrawArcs() {
|
|
157
|
+
self.drawArcs = !self.drawArcsSetting
|
|
158
|
+
},
|
|
146
159
|
afterAttach() {
|
|
147
160
|
addDisposer(
|
|
148
161
|
self,
|
|
@@ -217,6 +230,14 @@ const stateModelFactory = (
|
|
|
217
230
|
self.toggleDrawInterbaseCounts()
|
|
218
231
|
},
|
|
219
232
|
},
|
|
233
|
+
{
|
|
234
|
+
label: 'Draw arcs',
|
|
235
|
+
type: 'checkbox',
|
|
236
|
+
checked: self.drawArcsSetting,
|
|
237
|
+
onClick: () => {
|
|
238
|
+
self.toggleDrawArcs()
|
|
239
|
+
},
|
|
240
|
+
},
|
|
220
241
|
]
|
|
221
242
|
},
|
|
222
243
|
// The SNPCoverage filters are called twice because the BAM/CRAM
|
|
@@ -18,6 +18,8 @@ import { renderToAbstractCanvas } from '@jbrowse/core/util/offscreenCanvasUtils'
|
|
|
18
18
|
import { BaseLayout } from '@jbrowse/core/util/layouts/BaseLayout'
|
|
19
19
|
import { getAdapter } from '@jbrowse/core/data_adapters/dataAdapterCache'
|
|
20
20
|
import { readConfObject } from '@jbrowse/core/configuration'
|
|
21
|
+
|
|
22
|
+
// locals
|
|
21
23
|
import {
|
|
22
24
|
Mismatch,
|
|
23
25
|
parseCigar,
|
|
@@ -26,21 +28,12 @@ import {
|
|
|
26
28
|
} from '../BamAdapter/MismatchParser'
|
|
27
29
|
import { sortFeature } from './sortUtil'
|
|
28
30
|
import { getTagAlt, orientationTypes } from '../util'
|
|
29
|
-
|
|
30
31
|
import {
|
|
31
32
|
PileupLayoutSession,
|
|
32
33
|
PileupLayoutSessionProps,
|
|
33
34
|
} from './PileupLayoutSession'
|
|
34
35
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
35
36
|
|
|
36
|
-
export type {
|
|
37
|
-
RenderArgs,
|
|
38
|
-
RenderArgsSerialized,
|
|
39
|
-
RenderResults,
|
|
40
|
-
ResultsSerialized,
|
|
41
|
-
ResultsDeserialized,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
37
|
function getColorBaseMap(theme: Theme) {
|
|
45
38
|
return {
|
|
46
39
|
A: theme.palette.bases.A.main,
|
|
@@ -94,7 +87,7 @@ const alignmentColoring: { [key: string]: string } = {
|
|
|
94
87
|
color_pair_rr: 'navy',
|
|
95
88
|
color_pair_rl: 'teal',
|
|
96
89
|
color_pair_ll: 'green',
|
|
97
|
-
color_nostrand: '#
|
|
90
|
+
color_nostrand: '#c8c8c8',
|
|
98
91
|
color_interchrom: 'orange',
|
|
99
92
|
color_longinsert: 'red',
|
|
100
93
|
color_shortinsert: 'pink',
|
|
@@ -121,25 +114,38 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
121
114
|
return { charWidth, charHeight }
|
|
122
115
|
}
|
|
123
116
|
|
|
124
|
-
layoutFeature(
|
|
125
|
-
feature
|
|
126
|
-
layout
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
layoutFeature({
|
|
118
|
+
feature,
|
|
119
|
+
layout,
|
|
120
|
+
bpPerPx,
|
|
121
|
+
region,
|
|
122
|
+
showSoftClip,
|
|
123
|
+
heightPx,
|
|
124
|
+
displayMode,
|
|
125
|
+
}: {
|
|
126
|
+
feature: Feature
|
|
127
|
+
layout: BaseLayout<Feature>
|
|
128
|
+
bpPerPx: number
|
|
129
|
+
region: Region
|
|
130
|
+
showSoftClip?: boolean
|
|
131
|
+
heightPx: number
|
|
132
|
+
displayMode: string
|
|
133
|
+
}): LayoutRecord | null {
|
|
132
134
|
let expansionBefore = 0
|
|
133
135
|
let expansionAfter = 0
|
|
134
|
-
const mismatches: Mismatch[] = feature.get('mismatches')
|
|
135
|
-
const seq: string = feature.get('seq')
|
|
136
136
|
|
|
137
137
|
// Expand the start and end of feature when softclipping enabled
|
|
138
|
-
if (showSoftClip
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
if (showSoftClip) {
|
|
139
|
+
const mismatches = feature.get('mismatches') as Mismatch[]
|
|
140
|
+
const seq = feature.get('seq') as string
|
|
141
|
+
if (seq) {
|
|
142
|
+
for (let i = 0; i < mismatches.length; i += 1) {
|
|
143
|
+
const { type, start, cliplen = 0 } = mismatches[i]
|
|
144
|
+
if (type === 'softclip') {
|
|
145
|
+
start === 0
|
|
146
|
+
? (expansionBefore = cliplen)
|
|
147
|
+
: (expansionAfter = cliplen)
|
|
148
|
+
}
|
|
143
149
|
}
|
|
144
150
|
}
|
|
145
151
|
}
|
|
@@ -151,8 +157,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
151
157
|
bpPerPx,
|
|
152
158
|
)
|
|
153
159
|
|
|
154
|
-
let heightPx = readConfObject(config, 'height', { feature })
|
|
155
|
-
const displayMode = readConfObject(config, 'displayMode', { feature })
|
|
156
160
|
if (displayMode === 'compact') {
|
|
157
161
|
heightPx /= 3
|
|
158
162
|
}
|
|
@@ -495,9 +499,18 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
495
499
|
drawAlignmentRect(
|
|
496
500
|
ctx: CanvasRenderingContext2D,
|
|
497
501
|
feat: LayoutFeature,
|
|
498
|
-
props: RenderArgsDeserializedWithFeaturesAndLayout
|
|
502
|
+
props: RenderArgsDeserializedWithFeaturesAndLayout & {
|
|
503
|
+
defaultColor: boolean
|
|
504
|
+
},
|
|
499
505
|
) {
|
|
500
|
-
const {
|
|
506
|
+
const {
|
|
507
|
+
defaultColor,
|
|
508
|
+
config,
|
|
509
|
+
bpPerPx,
|
|
510
|
+
regions,
|
|
511
|
+
colorBy,
|
|
512
|
+
colorTagMap = {},
|
|
513
|
+
} = props
|
|
501
514
|
const { tag = '', type: colorType = '' } = colorBy || {}
|
|
502
515
|
const { feature } = feat
|
|
503
516
|
const region = regions[0]
|
|
@@ -554,7 +567,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
554
567
|
// fetchValues
|
|
555
568
|
else {
|
|
556
569
|
const foundValue = colorTagMap[val]
|
|
557
|
-
ctx.fillStyle = foundValue || 'color_nostrand'
|
|
570
|
+
ctx.fillStyle = foundValue || alignmentColoring['color_nostrand']
|
|
558
571
|
}
|
|
559
572
|
break
|
|
560
573
|
}
|
|
@@ -563,7 +576,12 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
563
576
|
|
|
564
577
|
case 'normal':
|
|
565
578
|
default:
|
|
566
|
-
|
|
579
|
+
if (defaultColor) {
|
|
580
|
+
// avoid a readConfObject call here
|
|
581
|
+
ctx.fillStyle = '#c8c8c8'
|
|
582
|
+
} else {
|
|
583
|
+
ctx.fillStyle = readConfObject(config, 'color', { feature })
|
|
584
|
+
}
|
|
567
585
|
break
|
|
568
586
|
}
|
|
569
587
|
|
|
@@ -596,21 +614,28 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
596
614
|
mismatchAlpha?: boolean
|
|
597
615
|
drawSNPs?: boolean
|
|
598
616
|
drawIndels?: boolean
|
|
617
|
+
minSubfeatureWidth: number
|
|
618
|
+
largeInsertionIndicatorScale: number
|
|
619
|
+
charWidth: number
|
|
620
|
+
charHeight: number
|
|
599
621
|
},
|
|
600
622
|
) {
|
|
601
|
-
const {
|
|
602
|
-
|
|
623
|
+
const {
|
|
624
|
+
minSubfeatureWidth: minWidth,
|
|
625
|
+
largeInsertionIndicatorScale,
|
|
626
|
+
mismatchAlpha,
|
|
627
|
+
drawSNPs = true,
|
|
628
|
+
drawIndels = true,
|
|
629
|
+
charWidth,
|
|
630
|
+
charHeight,
|
|
631
|
+
} = opts
|
|
632
|
+
const { bpPerPx, regions } = props
|
|
603
633
|
const { heightPx, topPx, feature } = feat
|
|
604
|
-
const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
|
|
605
634
|
const [region] = regions
|
|
606
635
|
const start = feature.get('start')
|
|
607
|
-
|
|
608
|
-
const insertionScale = readConfObject(
|
|
609
|
-
config,
|
|
610
|
-
'largeInsertionIndicatorScale',
|
|
611
|
-
)
|
|
636
|
+
|
|
612
637
|
const pxPerBp = Math.min(1 / bpPerPx, 2)
|
|
613
|
-
const w = Math.max(
|
|
638
|
+
const w = Math.max(minWidth, pxPerBp)
|
|
614
639
|
const mismatches: Mismatch[] = feature.get('mismatches')
|
|
615
640
|
const heightLim = charHeight - 2
|
|
616
641
|
|
|
@@ -629,50 +654,45 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
629
654
|
// insertion markers
|
|
630
655
|
for (let i = 0; i < mismatches.length; i += 1) {
|
|
631
656
|
const mismatch = mismatches[i]
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
)
|
|
638
|
-
const mismatchWidthPx = Math.max(
|
|
639
|
-
minFeatWidth,
|
|
640
|
-
Math.abs(mismatchLeftPx - mismatchRightPx),
|
|
641
|
-
)
|
|
657
|
+
const mstart = start + mismatch.start
|
|
658
|
+
const mlen = mismatch.length
|
|
659
|
+
const mbase = mismatch.base
|
|
660
|
+
const [leftPx, rightPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx)
|
|
661
|
+
const widthPx = Math.max(minWidth, Math.abs(leftPx - rightPx))
|
|
642
662
|
if (mismatch.type === 'mismatch' && drawSNPs) {
|
|
643
663
|
const baseColor = colorForBase[mismatch.base] || '#888'
|
|
644
664
|
|
|
645
665
|
ctx.fillStyle = getAlphaColor(baseColor, mismatch)
|
|
646
666
|
|
|
647
|
-
ctx.fillRect(
|
|
667
|
+
ctx.fillRect(leftPx, topPx, widthPx, heightPx)
|
|
648
668
|
|
|
649
|
-
if (
|
|
669
|
+
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
650
670
|
// normal SNP coloring
|
|
651
671
|
ctx.fillStyle = getAlphaColor(
|
|
652
672
|
theme.palette.getContrastText(baseColor),
|
|
653
673
|
mismatch,
|
|
654
674
|
)
|
|
655
675
|
ctx.fillText(
|
|
656
|
-
|
|
657
|
-
|
|
676
|
+
mbase,
|
|
677
|
+
leftPx + (widthPx - charWidth) / 2 + 1,
|
|
658
678
|
topPx + heightPx,
|
|
659
679
|
)
|
|
660
680
|
}
|
|
661
681
|
} else if (mismatch.type === 'deletion' && drawIndels) {
|
|
662
682
|
const baseColor = colorForBase.deletion
|
|
663
683
|
ctx.fillStyle = baseColor
|
|
664
|
-
ctx.fillRect(
|
|
665
|
-
if (
|
|
684
|
+
ctx.fillRect(leftPx, topPx, widthPx, heightPx)
|
|
685
|
+
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
666
686
|
ctx.fillStyle = theme.palette.getContrastText(baseColor)
|
|
667
687
|
ctx.fillText(
|
|
668
|
-
|
|
669
|
-
|
|
688
|
+
mbase,
|
|
689
|
+
leftPx + (widthPx - charWidth) / 2 + 1,
|
|
670
690
|
topPx + heightPx,
|
|
671
691
|
)
|
|
672
692
|
}
|
|
673
693
|
} else if (mismatch.type === 'insertion' && drawIndels) {
|
|
674
694
|
ctx.fillStyle = 'purple'
|
|
675
|
-
const pos =
|
|
695
|
+
const pos = leftPx - 1
|
|
676
696
|
const len = +mismatch.base || mismatch.length
|
|
677
697
|
if (len < 10) {
|
|
678
698
|
ctx.fillRect(pos, topPx, w, heightPx)
|
|
@@ -681,41 +701,33 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
681
701
|
ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
|
|
682
702
|
}
|
|
683
703
|
if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
|
|
684
|
-
ctx.fillText(
|
|
685
|
-
`(${mismatch.base})`,
|
|
686
|
-
mismatchLeftPx + 2,
|
|
687
|
-
topPx + heightPx,
|
|
688
|
-
)
|
|
704
|
+
ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
|
|
689
705
|
}
|
|
690
706
|
}
|
|
691
707
|
} else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
|
|
692
708
|
ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
|
|
693
|
-
const pos =
|
|
709
|
+
const pos = leftPx - 1
|
|
694
710
|
ctx.fillRect(pos, topPx + 1, w, heightPx - 2)
|
|
695
711
|
ctx.fillRect(pos - w, topPx, w * 3, 1)
|
|
696
712
|
ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
|
|
697
|
-
if (
|
|
698
|
-
ctx.fillText(
|
|
699
|
-
`(${mismatch.base})`,
|
|
700
|
-
mismatchLeftPx + 2,
|
|
701
|
-
topPx + heightPx,
|
|
702
|
-
)
|
|
713
|
+
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
714
|
+
ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
|
|
703
715
|
}
|
|
704
716
|
} else if (mismatch.type === 'skip') {
|
|
705
717
|
// fix to avoid bad rendering
|
|
706
718
|
// note that this was also related to chrome bug https://bugs.chromium.org/p/chro>
|
|
707
719
|
// ref #1236
|
|
708
|
-
if (
|
|
720
|
+
if (leftPx + widthPx > 0) {
|
|
721
|
+
// make small exons more visible when zoomed far out
|
|
709
722
|
ctx.clearRect(
|
|
710
|
-
|
|
723
|
+
leftPx,
|
|
711
724
|
topPx,
|
|
712
|
-
|
|
713
|
-
mismatchWidthPx - (bpPerPx > 10 ? 1.5 : 0),
|
|
725
|
+
widthPx - (bpPerPx > 10 ? 1.5 : 0),
|
|
714
726
|
heightPx,
|
|
715
727
|
)
|
|
716
728
|
}
|
|
717
729
|
ctx.fillStyle = '#333'
|
|
718
|
-
ctx.fillRect(
|
|
730
|
+
ctx.fillRect(leftPx, topPx + heightPx / 2, widthPx, 2)
|
|
719
731
|
}
|
|
720
732
|
}
|
|
721
733
|
|
|
@@ -723,34 +735,31 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
723
735
|
if (drawIndels) {
|
|
724
736
|
for (let i = 0; i < mismatches.length; i += 1) {
|
|
725
737
|
const mismatch = mismatches[i]
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
region,
|
|
730
|
-
bpPerPx,
|
|
731
|
-
)
|
|
738
|
+
const mstart = start + mismatch.start
|
|
739
|
+
const mlen = mismatch.length
|
|
740
|
+
const [leftPx] = bpSpanPx(mstart, mstart + mlen, region, bpPerPx)
|
|
732
741
|
const len = +mismatch.base || mismatch.length
|
|
733
742
|
const txt = `${len}`
|
|
734
743
|
if (mismatch.type === 'insertion' && len >= 10) {
|
|
735
|
-
if (bpPerPx >
|
|
744
|
+
if (bpPerPx > largeInsertionIndicatorScale) {
|
|
736
745
|
ctx.fillStyle = 'purple'
|
|
737
|
-
ctx.fillRect(
|
|
746
|
+
ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
|
|
738
747
|
} else if (heightPx > charHeight) {
|
|
739
748
|
const rect = ctx.measureText(txt)
|
|
740
749
|
const padding = 5
|
|
741
750
|
ctx.fillStyle = 'purple'
|
|
742
751
|
ctx.fillRect(
|
|
743
|
-
|
|
752
|
+
leftPx - rect.width / 2 - padding,
|
|
744
753
|
topPx,
|
|
745
754
|
rect.width + 2 * padding,
|
|
746
755
|
heightPx,
|
|
747
756
|
)
|
|
748
757
|
ctx.fillStyle = 'white'
|
|
749
|
-
ctx.fillText(txt,
|
|
758
|
+
ctx.fillText(txt, leftPx - rect.width / 2, topPx + heightPx)
|
|
750
759
|
} else {
|
|
751
760
|
const padding = 2
|
|
752
761
|
ctx.fillStyle = 'purple'
|
|
753
|
-
ctx.fillRect(
|
|
762
|
+
ctx.fillRect(leftPx - padding, topPx, 2 * padding, heightPx)
|
|
754
763
|
}
|
|
755
764
|
}
|
|
756
765
|
}
|
|
@@ -831,11 +840,15 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
831
840
|
|
|
832
841
|
async makeImageData(
|
|
833
842
|
ctx: CanvasRenderingContext2D,
|
|
834
|
-
layoutRecords:
|
|
843
|
+
layoutRecords: (LayoutFeature | null)[],
|
|
835
844
|
props: RenderArgsDeserializedWithFeaturesAndLayout,
|
|
836
845
|
) {
|
|
837
846
|
const { layout, config, showSoftClip, colorBy, theme: configTheme } = props
|
|
838
847
|
const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
|
|
848
|
+
const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
|
|
849
|
+
const insertScale = readConfObject(config, 'largeInsertionIndicatorScale')
|
|
850
|
+
const defaultColor = readConfObject(config, 'color') === '#f0f'
|
|
851
|
+
|
|
839
852
|
const theme = createJBrowseTheme(configTheme)
|
|
840
853
|
const colorForBase = getColorBaseMap(theme)
|
|
841
854
|
if (!layout) {
|
|
@@ -845,21 +858,25 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
845
858
|
throw new Error('invalid layout object')
|
|
846
859
|
}
|
|
847
860
|
ctx.font = 'bold 10px Courier New,monospace'
|
|
848
|
-
|
|
849
|
-
|
|
861
|
+
|
|
862
|
+
const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
|
|
863
|
+
layoutRecords.forEach(feat => {
|
|
850
864
|
if (feat === null) {
|
|
851
865
|
return
|
|
852
866
|
}
|
|
853
867
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
868
|
+
this.drawAlignmentRect(ctx, feat, {
|
|
869
|
+
...props,
|
|
870
|
+
defaultColor,
|
|
871
|
+
})
|
|
858
872
|
this.drawMismatches(ctx, feat, props, theme, colorForBase, {
|
|
859
873
|
mismatchAlpha,
|
|
860
|
-
|
|
861
874
|
drawSNPs: shouldDrawMismatches(colorBy?.type),
|
|
862
875
|
drawIndels: shouldDrawMismatches(colorBy?.type),
|
|
876
|
+
largeInsertionIndicatorScale: insertScale,
|
|
877
|
+
minSubfeatureWidth,
|
|
878
|
+
charWidth,
|
|
879
|
+
charHeight,
|
|
863
880
|
})
|
|
864
881
|
if (showSoftClip) {
|
|
865
882
|
this.drawSoftClipping(ctx, feat, props, config, theme)
|
|
@@ -887,59 +904,60 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
887
904
|
throw new Error('invalid layout object')
|
|
888
905
|
}
|
|
889
906
|
|
|
890
|
-
const
|
|
891
|
-
sortedBy
|
|
907
|
+
const featureMap =
|
|
908
|
+
sortedBy?.type && region.start === sortedBy.pos
|
|
892
909
|
? sortFeature(features, sortedBy)
|
|
893
|
-
:
|
|
894
|
-
|
|
910
|
+
: features
|
|
911
|
+
|
|
912
|
+
const heightPx = readConfObject(config, 'height')
|
|
913
|
+
const displayMode = readConfObject(config, 'displayMode')
|
|
895
914
|
const layoutRecords = iterMap(
|
|
896
915
|
featureMap.values(),
|
|
897
916
|
feature =>
|
|
898
|
-
this.layoutFeature(
|
|
917
|
+
this.layoutFeature({
|
|
899
918
|
feature,
|
|
900
919
|
layout,
|
|
901
|
-
config,
|
|
902
920
|
bpPerPx,
|
|
903
921
|
region,
|
|
904
922
|
showSoftClip,
|
|
905
|
-
|
|
923
|
+
heightPx,
|
|
924
|
+
displayMode,
|
|
925
|
+
}),
|
|
906
926
|
featureMap.size,
|
|
907
927
|
)
|
|
908
928
|
return layoutRecords
|
|
909
929
|
}
|
|
910
930
|
|
|
911
931
|
async render(renderProps: RenderArgsDeserialized) {
|
|
912
|
-
const { bpPerPx, regions } = renderProps
|
|
932
|
+
const { sessionId, bpPerPx, regions, adapterConfig } = renderProps
|
|
933
|
+
const { sequenceAdapter } = adapterConfig
|
|
913
934
|
const features = await this.getFeatures(renderProps)
|
|
914
935
|
const layout = this.createLayoutInWorker(renderProps)
|
|
915
936
|
|
|
916
937
|
const layoutRecords = this.layoutFeats({ ...renderProps, features, layout })
|
|
917
|
-
|
|
918
|
-
// @ts-ignore
|
|
919
|
-
const { dataAdapter: sequenceAdapter } = renderProps.adapterConfig
|
|
920
|
-
.sequenceAdapter
|
|
921
|
-
? await getAdapter(
|
|
922
|
-
this.pluginManager,
|
|
923
|
-
renderProps.sessionId,
|
|
924
|
-
// @ts-ignore
|
|
925
|
-
renderProps.adapterConfig.sequenceAdapter,
|
|
926
|
-
)
|
|
927
|
-
: {}
|
|
928
938
|
const [region] = regions
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
939
|
+
let regionSequence: string | undefined
|
|
940
|
+
const { end, start, originalRefName, refName } = region
|
|
941
|
+
|
|
942
|
+
if (sequenceAdapter) {
|
|
943
|
+
const { dataAdapter } = await getAdapter(
|
|
944
|
+
this.pluginManager,
|
|
945
|
+
sessionId,
|
|
946
|
+
sequenceAdapter,
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
const feats = await (dataAdapter as BaseFeatureDataAdapter)
|
|
950
|
+
.getFeatures({
|
|
951
|
+
...region,
|
|
952
|
+
refName: originalRefName || refName,
|
|
953
|
+
end: region.end + 1,
|
|
954
|
+
})
|
|
955
|
+
.pipe(toArray())
|
|
956
|
+
.toPromise()
|
|
957
|
+
regionSequence = feats[0]?.get('seq')
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const width = (end - start) / bpPerPx
|
|
943
961
|
const height = Math.max(layout.getTotalHeight(), 1)
|
|
944
962
|
|
|
945
963
|
const res = await renderToAbstractCanvas(
|
|
@@ -979,3 +997,11 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
979
997
|
return new PileupLayoutSession(args)
|
|
980
998
|
}
|
|
981
999
|
}
|
|
1000
|
+
|
|
1001
|
+
export type {
|
|
1002
|
+
RenderArgs,
|
|
1003
|
+
RenderArgsSerialized,
|
|
1004
|
+
RenderResults,
|
|
1005
|
+
ResultsSerialized,
|
|
1006
|
+
ResultsDeserialized,
|
|
1007
|
+
}
|
|
@@ -158,8 +158,10 @@ function PileupRendering(props: {
|
|
|
158
158
|
|
|
159
159
|
function callMouseHandler(handlerName: string, event: MouseEvent) {
|
|
160
160
|
// @ts-ignore
|
|
161
|
+
// eslint-disable-next-line react/destructuring-assignment
|
|
161
162
|
const featureHandler = props[`onFeature${handlerName}`]
|
|
162
163
|
// @ts-ignore
|
|
164
|
+
// eslint-disable-next-line react/destructuring-assignment
|
|
163
165
|
const canvasHandler = props[`on${handlerName}`]
|
|
164
166
|
if (featureHandler && featureIdUnderMouse) {
|
|
165
167
|
featureHandler(event, featureIdUnderMouse)
|