@jbrowse/plugin-alignments 1.7.7 → 1.7.8
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/BamAdapter/BamAdapter.d.ts +1 -1
- package/dist/BamAdapter/BamAdapter.js +3 -3
- package/dist/BamAdapter/MismatchParser.d.ts +2 -5
- package/dist/BamAdapter/MismatchParser.js +104 -44
- package/dist/BamAdapter/MismatchParser.test.js +6 -14
- package/dist/CramAdapter/CramAdapter.d.ts +10 -9
- package/dist/CramAdapter/CramAdapter.js +6 -6
- package/dist/CramAdapter/CramSlightlyLazyFeature.js +35 -30
- package/dist/LinearPileupDisplay/model.js +1 -1
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +2 -3
- package/dist/LinearSNPCoverageDisplay/models/model.js +1 -1
- package/dist/PileupRenderer/PileupLayoutSession.d.ts +3 -0
- package/dist/PileupRenderer/PileupLayoutSession.js +3 -1
- package/dist/PileupRenderer/PileupRenderer.d.ts +1 -1
- package/dist/PileupRenderer/PileupRenderer.js +125 -162
- package/dist/PileupRenderer/configSchema.js +2 -2
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +4 -6
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +92 -96
- package/package.json +3 -3
- package/src/BamAdapter/BamAdapter.ts +3 -3
- package/src/BamAdapter/MismatchParser.test.ts +5 -7
- package/src/BamAdapter/MismatchParser.ts +69 -58
- package/src/CramAdapter/CramAdapter.ts +14 -10
- package/src/CramAdapter/CramSlightlyLazyFeature.ts +84 -91
- package/src/LinearPileupDisplay/model.ts +1 -1
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +32 -25
- package/src/LinearSNPCoverageDisplay/models/model.ts +1 -1
- package/src/PileupRenderer/PileupLayoutSession.ts +6 -1
- package/src/PileupRenderer/PileupRenderer.tsx +70 -68
- package/src/PileupRenderer/configSchema.ts +2 -2
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +84 -76
|
@@ -131,7 +131,8 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
131
131
|
let sublen = 0
|
|
132
132
|
if (typeof this.record.readFeatures !== 'undefined') {
|
|
133
133
|
// @ts-ignore
|
|
134
|
-
this.record.readFeatures.
|
|
134
|
+
for (let i = 0; i < this.record.readFeatures.length; i++) {
|
|
135
|
+
const { code, refPos, sub, data } = this.record.readFeatures[i]
|
|
135
136
|
sublen = refPos - last_pos
|
|
136
137
|
seq += ref.substring(last_pos - refStart, refPos - refStart)
|
|
137
138
|
last_pos = refPos
|
|
@@ -200,7 +201,7 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
200
201
|
cigar += `${data}H`
|
|
201
202
|
oplen = 0
|
|
202
203
|
} // else q or Q
|
|
203
|
-
}
|
|
204
|
+
}
|
|
204
205
|
} else {
|
|
205
206
|
sublen = this.record.readLength - seq.length
|
|
206
207
|
}
|
|
@@ -303,95 +304,87 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
303
304
|
return []
|
|
304
305
|
}
|
|
305
306
|
const start = this.get('start')
|
|
306
|
-
const mismatches: Mismatch[] =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
mismatches.push({
|
|
322
|
-
start: refPos,
|
|
323
|
-
length: 1,
|
|
324
|
-
base: sub,
|
|
325
|
-
qual: qual?.[pos],
|
|
326
|
-
altbase: ref,
|
|
327
|
-
type: 'mismatch',
|
|
328
|
-
})
|
|
329
|
-
} else if (code === 'I') {
|
|
330
|
-
// insertion
|
|
331
|
-
mismatches.push({
|
|
332
|
-
start: refPos,
|
|
333
|
-
type: 'insertion',
|
|
334
|
-
base: `${data.length}`,
|
|
335
|
-
length: 0,
|
|
336
|
-
})
|
|
337
|
-
} else if (code === 'N') {
|
|
338
|
-
// reference skip
|
|
339
|
-
mismatches.push({
|
|
340
|
-
type: 'skip',
|
|
341
|
-
length: data,
|
|
342
|
-
start: refPos,
|
|
343
|
-
base: 'N',
|
|
344
|
-
})
|
|
345
|
-
} else if (code === 'S') {
|
|
346
|
-
// soft clip
|
|
347
|
-
const len = data.length
|
|
348
|
-
mismatches.push({
|
|
349
|
-
start: refPos,
|
|
350
|
-
type: 'softclip',
|
|
351
|
-
base: `S${len}`,
|
|
352
|
-
cliplen: len,
|
|
353
|
-
length: 1,
|
|
354
|
-
})
|
|
355
|
-
} else if (code === 'P') {
|
|
356
|
-
// padding
|
|
357
|
-
} else if (code === 'H') {
|
|
358
|
-
// hard clip
|
|
359
|
-
const len = data
|
|
360
|
-
mismatches.push({
|
|
361
|
-
start: refPos,
|
|
362
|
-
type: 'hardclip',
|
|
363
|
-
base: `H${len}`,
|
|
364
|
-
cliplen: len,
|
|
365
|
-
length: 1,
|
|
366
|
-
})
|
|
367
|
-
} else if (code === 'D') {
|
|
368
|
-
// deletion
|
|
369
|
-
mismatches.push({
|
|
370
|
-
type: 'deletion',
|
|
371
|
-
length: data,
|
|
372
|
-
start: refPos,
|
|
373
|
-
base: '*',
|
|
374
|
-
})
|
|
375
|
-
} else if (code === 'b') {
|
|
376
|
-
// stretch of bases
|
|
377
|
-
} else if (code === 'q') {
|
|
378
|
-
// stretch of qual scores
|
|
379
|
-
} else if (code === 'B') {
|
|
380
|
-
// a pair of [base, qual]
|
|
381
|
-
} else if (code === 'i') {
|
|
382
|
-
// single-base insertion
|
|
383
|
-
// insertion
|
|
384
|
-
mismatches.push({
|
|
385
|
-
start: refPos,
|
|
386
|
-
type: 'insertion',
|
|
387
|
-
base: data,
|
|
388
|
-
length: 1,
|
|
389
|
-
})
|
|
390
|
-
} else if (code === 'Q') {
|
|
391
|
-
// single quality value
|
|
307
|
+
const mismatches: Mismatch[] = new Array(readFeatures.length)
|
|
308
|
+
let j = 0
|
|
309
|
+
for (let i = 0; i < readFeatures.length; i++) {
|
|
310
|
+
const f = readFeatures[i]
|
|
311
|
+
const { code, pos, data, sub, ref } = f
|
|
312
|
+
const refPos = f.refPos - 1 - start
|
|
313
|
+
if (code === 'X') {
|
|
314
|
+
// substitution
|
|
315
|
+
mismatches[j++] = {
|
|
316
|
+
start: refPos,
|
|
317
|
+
length: 1,
|
|
318
|
+
base: sub,
|
|
319
|
+
qual: qual?.[pos],
|
|
320
|
+
altbase: ref,
|
|
321
|
+
type: 'mismatch',
|
|
392
322
|
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
323
|
+
} else if (code === 'I') {
|
|
324
|
+
// insertion
|
|
325
|
+
mismatches[j++] = {
|
|
326
|
+
start: refPos,
|
|
327
|
+
type: 'insertion',
|
|
328
|
+
base: `${data.length}`,
|
|
329
|
+
length: 0,
|
|
330
|
+
}
|
|
331
|
+
} else if (code === 'N') {
|
|
332
|
+
// reference skip
|
|
333
|
+
mismatches[j++] = {
|
|
334
|
+
type: 'skip',
|
|
335
|
+
length: data,
|
|
336
|
+
start: refPos,
|
|
337
|
+
base: 'N',
|
|
338
|
+
}
|
|
339
|
+
} else if (code === 'S') {
|
|
340
|
+
// soft clip
|
|
341
|
+
const len = data.length
|
|
342
|
+
mismatches[j++] = {
|
|
343
|
+
start: refPos,
|
|
344
|
+
type: 'softclip',
|
|
345
|
+
base: `S${len}`,
|
|
346
|
+
cliplen: len,
|
|
347
|
+
length: 1,
|
|
348
|
+
}
|
|
349
|
+
} else if (code === 'P') {
|
|
350
|
+
// padding
|
|
351
|
+
} else if (code === 'H') {
|
|
352
|
+
// hard clip
|
|
353
|
+
const len = data
|
|
354
|
+
mismatches[j++] = {
|
|
355
|
+
start: refPos,
|
|
356
|
+
type: 'hardclip',
|
|
357
|
+
base: `H${len}`,
|
|
358
|
+
cliplen: len,
|
|
359
|
+
length: 1,
|
|
360
|
+
}
|
|
361
|
+
} else if (code === 'D') {
|
|
362
|
+
// deletion
|
|
363
|
+
mismatches[j++] = {
|
|
364
|
+
type: 'deletion',
|
|
365
|
+
length: data,
|
|
366
|
+
start: refPos,
|
|
367
|
+
base: '*',
|
|
368
|
+
}
|
|
369
|
+
} else if (code === 'b') {
|
|
370
|
+
// stretch of bases
|
|
371
|
+
} else if (code === 'q') {
|
|
372
|
+
// stretch of qual scores
|
|
373
|
+
} else if (code === 'B') {
|
|
374
|
+
// a pair of [base, qual]
|
|
375
|
+
} else if (code === 'i') {
|
|
376
|
+
// single-base insertion
|
|
377
|
+
// insertion
|
|
378
|
+
mismatches[j++] = {
|
|
379
|
+
start: refPos,
|
|
380
|
+
type: 'insertion',
|
|
381
|
+
base: data,
|
|
382
|
+
length: 1,
|
|
383
|
+
}
|
|
384
|
+
} else if (code === 'Q') {
|
|
385
|
+
// single quality value
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return mismatches.slice(0, j)
|
|
396
389
|
}
|
|
397
390
|
}
|
|
@@ -409,7 +409,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
409
409
|
displayModel: self,
|
|
410
410
|
sortedBy,
|
|
411
411
|
colorBy,
|
|
412
|
-
filterBy,
|
|
412
|
+
filterBy: JSON.parse(JSON.stringify(filterBy)),
|
|
413
413
|
colorTagMap: JSON.parse(JSON.stringify(colorTagMap)),
|
|
414
414
|
modificationTagMap: JSON.parse(JSON.stringify(modificationTagMap)),
|
|
415
415
|
showSoftClip: self.showSoftClipping,
|
|
@@ -7,7 +7,6 @@ import { Tooltip } from '@jbrowse/plugin-wiggle'
|
|
|
7
7
|
type Count = {
|
|
8
8
|
[key: string]: {
|
|
9
9
|
total: number
|
|
10
|
-
strands: { [key: string]: number }
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
@@ -18,6 +17,9 @@ type SNPInfo = {
|
|
|
18
17
|
noncov: Count
|
|
19
18
|
delskips: Count
|
|
20
19
|
total: number
|
|
20
|
+
'-1': number
|
|
21
|
+
'0': number
|
|
22
|
+
'1': number
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const en = (n: number) => n.toLocaleString('en-US')
|
|
@@ -50,33 +52,38 @@ const TooltipContents = React.forwardRef(
|
|
|
50
52
|
<tbody>
|
|
51
53
|
<tr>
|
|
52
54
|
<td>Total</td>
|
|
53
|
-
<td>{total}</td>
|
|
55
|
+
<td>{info.total}</td>
|
|
56
|
+
</tr>
|
|
57
|
+
<tr>
|
|
58
|
+
<td>REF</td>
|
|
59
|
+
<td>{info.ref}</td>
|
|
60
|
+
<td>
|
|
61
|
+
{info['-1'] ? `${info['-1']}(-)` : ''}
|
|
62
|
+
{info['1'] ? `${info['1']}(+)` : ''}
|
|
63
|
+
</td>
|
|
54
64
|
<td />
|
|
55
65
|
</tr>
|
|
56
66
|
|
|
57
|
-
{Object.entries(info).map(([key, entry]) =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
})
|
|
79
|
-
})}
|
|
67
|
+
{Object.entries(info).map(([key, entry]) =>
|
|
68
|
+
Object.entries(entry).map(([base, score]) => (
|
|
69
|
+
<tr key={base}>
|
|
70
|
+
<td>{base.toUpperCase()}</td>
|
|
71
|
+
<td>{score.total}</td>
|
|
72
|
+
<td>
|
|
73
|
+
{base === 'total' || base === 'skip'
|
|
74
|
+
? '---'
|
|
75
|
+
: `${Math.floor(
|
|
76
|
+
(score.total / (total || score.total || 1)) * 100,
|
|
77
|
+
)}%`}
|
|
78
|
+
</td>
|
|
79
|
+
<td>
|
|
80
|
+
{score['-1'] ? `${score['-1']}(-)` : ''}
|
|
81
|
+
{score['1'] ? `${score['1']}(+)` : ''}
|
|
82
|
+
</td>
|
|
83
|
+
<td>{key}</td>
|
|
84
|
+
</tr>
|
|
85
|
+
)),
|
|
86
|
+
)}
|
|
80
87
|
</tbody>
|
|
81
88
|
</table>
|
|
82
89
|
</div>
|
|
@@ -143,7 +143,7 @@ const stateModelFactory = (
|
|
|
143
143
|
// must use getSnapshot because otherwise changes to e.g. just the
|
|
144
144
|
// colorBy.type are not read
|
|
145
145
|
colorBy: self.colorBy ? getSnapshot(self.colorBy) : undefined,
|
|
146
|
-
filterBy: self.filterBy,
|
|
146
|
+
filterBy: self.filterBy ? getSnapshot(self.filterBy) : undefined,
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
149
|
}
|
|
@@ -10,6 +10,7 @@ export interface PileupLayoutSessionProps {
|
|
|
10
10
|
config: AnyConfigurationModel
|
|
11
11
|
bpPerPx: number
|
|
12
12
|
filters: SerializableFilterChain
|
|
13
|
+
filterBy: unknown
|
|
13
14
|
sortedBy: unknown
|
|
14
15
|
showSoftClip: unknown
|
|
15
16
|
}
|
|
@@ -19,6 +20,7 @@ interface CachedPileupLayout {
|
|
|
19
20
|
layout: MyMultiLayout
|
|
20
21
|
config: AnyConfigurationModel
|
|
21
22
|
filters: SerializableFilterChain
|
|
23
|
+
filterBy: unknown
|
|
22
24
|
sortedBy: unknown
|
|
23
25
|
showSoftClip: boolean
|
|
24
26
|
}
|
|
@@ -26,6 +28,7 @@ interface CachedPileupLayout {
|
|
|
26
28
|
// Adds extra conditions to see if cached layout is valid
|
|
27
29
|
export class PileupLayoutSession extends LayoutSession {
|
|
28
30
|
sortedBy: unknown
|
|
31
|
+
filterBy: unknown
|
|
29
32
|
|
|
30
33
|
showSoftClip = false
|
|
31
34
|
|
|
@@ -38,7 +41,8 @@ export class PileupLayoutSession extends LayoutSession {
|
|
|
38
41
|
return (
|
|
39
42
|
super.cachedLayoutIsValid(cachedLayout) &&
|
|
40
43
|
this.showSoftClip === cachedLayout.showSoftClip &&
|
|
41
|
-
deepEqual(this.sortedBy, cachedLayout.sortedBy)
|
|
44
|
+
deepEqual(this.sortedBy, cachedLayout.sortedBy) &&
|
|
45
|
+
deepEqual(this.filterBy, cachedLayout.filterBy)
|
|
42
46
|
)
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -50,6 +54,7 @@ export class PileupLayoutSession extends LayoutSession {
|
|
|
50
54
|
layout: this.makeLayout(),
|
|
51
55
|
config: readConfObject(this.config),
|
|
52
56
|
filters: this.filters,
|
|
57
|
+
filterBy: this.filterBy,
|
|
53
58
|
sortedBy: this.sortedBy,
|
|
54
59
|
showSoftClip: this.showSoftClip,
|
|
55
60
|
}
|
|
@@ -118,10 +118,14 @@ interface LayoutFeature {
|
|
|
118
118
|
feature: Feature
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function
|
|
121
|
+
function shouldDrawSNPs(type?: string) {
|
|
122
122
|
return !['methylation', 'modifications'].includes(type || '')
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function shouldDrawIndels(type?: string) {
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
125
129
|
export default class PileupRenderer extends BoxRendererType {
|
|
126
130
|
supportsSVG = true
|
|
127
131
|
|
|
@@ -417,7 +421,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
417
421
|
|
|
418
422
|
const cigar = feature.get('CIGAR')
|
|
419
423
|
const start = feature.get('start')
|
|
420
|
-
const end = feature.get('end')
|
|
421
424
|
const seq = feature.get('seq')
|
|
422
425
|
const strand = feature.get('strand')
|
|
423
426
|
const cigarOps = parseCigar(cigar)
|
|
@@ -431,22 +434,20 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
431
434
|
const col = modificationTagMap[type] || 'black'
|
|
432
435
|
const base = Color(col)
|
|
433
436
|
for (const readPos of getNextRefPos(cigarOps, positions)) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
|
|
449
|
-
}
|
|
437
|
+
const r = start + readPos
|
|
438
|
+
const [leftPx, rightPx] = bpSpanPx(r, r + 1, region, bpPerPx)
|
|
439
|
+
|
|
440
|
+
// give it a little boost of 0.1 to not make them fully
|
|
441
|
+
// invisible to avoid confusion
|
|
442
|
+
const prob = probabilities[probIndex]
|
|
443
|
+
ctx.fillStyle =
|
|
444
|
+
prob && prob !== 1
|
|
445
|
+
? base
|
|
446
|
+
.alpha(prob + 0.1)
|
|
447
|
+
.hsl()
|
|
448
|
+
.string()
|
|
449
|
+
: col
|
|
450
|
+
ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
|
|
450
451
|
probIndex++
|
|
451
452
|
}
|
|
452
453
|
}
|
|
@@ -478,15 +479,14 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
478
479
|
const seq = feature.get('seq')
|
|
479
480
|
const strand = feature.get('strand')
|
|
480
481
|
const cigarOps = parseCigar(cigar)
|
|
481
|
-
const { start: rstart, end: rend } = region
|
|
482
482
|
|
|
483
|
-
const methBins = new Array(
|
|
483
|
+
const methBins = new Array(region.end - region.start).fill(0)
|
|
484
484
|
const modifications = getModificationPositions(mm, seq, strand)
|
|
485
485
|
for (let i = 0; i < modifications.length; i++) {
|
|
486
486
|
const { type, positions } = modifications[i]
|
|
487
487
|
if (type === 'm' && positions) {
|
|
488
488
|
for (const pos of getNextRefPos(cigarOps, positions)) {
|
|
489
|
-
const epos = pos + fstart -
|
|
489
|
+
const epos = pos + fstart - region.start
|
|
490
490
|
if (epos >= 0 && epos < methBins.length) {
|
|
491
491
|
methBins[epos] = 1
|
|
492
492
|
}
|
|
@@ -495,7 +495,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
for (let j = fstart; j < fend; j++) {
|
|
498
|
-
const i = j -
|
|
498
|
+
const i = j - region.start
|
|
499
499
|
if (i >= 0 && i < methBins.length) {
|
|
500
500
|
const l1 = regionSequence[i].toLowerCase()
|
|
501
501
|
const l2 = regionSequence[i + 1].toLowerCase()
|
|
@@ -503,13 +503,9 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
503
503
|
// if we are zoomed out, display just a block over the cpg
|
|
504
504
|
if (bpPerPx > 2) {
|
|
505
505
|
if (l1 === 'c' && l2 === 'g') {
|
|
506
|
-
const s =
|
|
506
|
+
const s = region.start + i
|
|
507
507
|
const [leftPx, rightPx] = bpSpanPx(s, s + 2, region, bpPerPx)
|
|
508
|
-
|
|
509
|
-
ctx.fillStyle = 'red'
|
|
510
|
-
} else {
|
|
511
|
-
ctx.fillStyle = 'blue'
|
|
512
|
-
}
|
|
508
|
+
ctx.fillStyle = methBins[i] || methBins[i + 1] ? 'red' : 'blue'
|
|
513
509
|
ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
|
|
514
510
|
}
|
|
515
511
|
}
|
|
@@ -517,21 +513,13 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
517
513
|
else {
|
|
518
514
|
// color
|
|
519
515
|
if (l1 === 'c' && l2 === 'g') {
|
|
520
|
-
const s =
|
|
516
|
+
const s = region.start + i
|
|
521
517
|
const [leftPx, rightPx] = bpSpanPx(s, s + 1, region, bpPerPx)
|
|
522
|
-
|
|
523
|
-
ctx.fillStyle = 'red'
|
|
524
|
-
} else {
|
|
525
|
-
ctx.fillStyle = 'blue'
|
|
526
|
-
}
|
|
518
|
+
ctx.fillStyle = methBins[i] ? 'red' : 'blue'
|
|
527
519
|
ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
|
|
528
520
|
|
|
529
521
|
const [leftPx2, rightPx2] = bpSpanPx(s + 1, s + 2, region, bpPerPx)
|
|
530
|
-
|
|
531
|
-
ctx.fillStyle = 'red'
|
|
532
|
-
} else {
|
|
533
|
-
ctx.fillStyle = 'blue'
|
|
534
|
-
}
|
|
522
|
+
ctx.fillStyle = methBins[i + 1] ? 'red' : 'blue'
|
|
535
523
|
ctx.fillRect(leftPx2, topPx, rightPx2 - leftPx2 + 0.5, heightPx)
|
|
536
524
|
}
|
|
537
525
|
}
|
|
@@ -754,17 +742,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
754
742
|
const mismatches: Mismatch[] = feature.get('mismatches')
|
|
755
743
|
const heightLim = charHeight - 2
|
|
756
744
|
|
|
757
|
-
function getAlphaColor(baseColor: string, mismatch: { qual?: number }) {
|
|
758
|
-
let color = baseColor
|
|
759
|
-
if (mismatchAlpha && mismatch.qual !== undefined) {
|
|
760
|
-
color = Color(baseColor)
|
|
761
|
-
.alpha(Math.min(1, mismatch.qual / 50))
|
|
762
|
-
.hsl()
|
|
763
|
-
.string()
|
|
764
|
-
}
|
|
765
|
-
return color
|
|
766
|
-
}
|
|
767
|
-
|
|
768
745
|
// extraHorizontallyFlippedOffset is used to draw interbase items, which
|
|
769
746
|
// are located to the left when forward and right when reversed
|
|
770
747
|
const extraHorizontallyFlippedOffset = region.reversed
|
|
@@ -783,14 +760,28 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
783
760
|
if (mismatch.type === 'mismatch' && drawSNPs) {
|
|
784
761
|
const baseColor = colorForBase[mismatch.base] || '#888'
|
|
785
762
|
|
|
786
|
-
ctx.fillStyle =
|
|
763
|
+
ctx.fillStyle = !mismatchAlpha
|
|
764
|
+
? baseColor
|
|
765
|
+
: mismatch.qual !== undefined
|
|
766
|
+
? Color(baseColor)
|
|
767
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
768
|
+
.hsl()
|
|
769
|
+
.string()
|
|
770
|
+
: baseColor
|
|
787
771
|
|
|
788
772
|
ctx.fillRect(leftPx, topPx, widthPx, heightPx)
|
|
789
773
|
|
|
790
774
|
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
791
775
|
// normal SNP coloring
|
|
792
|
-
const
|
|
793
|
-
ctx.fillStyle =
|
|
776
|
+
const contrastColor = contrastForBase[mismatch.base] || 'black'
|
|
777
|
+
ctx.fillStyle = !mismatchAlpha
|
|
778
|
+
? contrastColor
|
|
779
|
+
: mismatch.qual !== undefined
|
|
780
|
+
? Color(contrastColor)
|
|
781
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
782
|
+
.hsl()
|
|
783
|
+
.string()
|
|
784
|
+
: contrastColor
|
|
794
785
|
ctx.fillText(
|
|
795
786
|
mbase,
|
|
796
787
|
leftPx + (widthPx - charWidth) / 2 + 1,
|
|
@@ -869,17 +860,17 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
869
860
|
ctx.fillStyle = 'purple'
|
|
870
861
|
ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
|
|
871
862
|
} else if (heightPx > charHeight) {
|
|
872
|
-
const
|
|
863
|
+
const rwidth = measureText(txt)
|
|
873
864
|
const padding = 5
|
|
874
865
|
ctx.fillStyle = 'purple'
|
|
875
866
|
ctx.fillRect(
|
|
876
|
-
leftPx -
|
|
867
|
+
leftPx - rwidth / 2 - padding,
|
|
877
868
|
topPx,
|
|
878
|
-
|
|
869
|
+
rwidth + 2 * padding,
|
|
879
870
|
heightPx,
|
|
880
871
|
)
|
|
881
872
|
ctx.fillStyle = 'white'
|
|
882
|
-
ctx.fillText(txt, leftPx -
|
|
873
|
+
ctx.fillText(txt, leftPx - rwidth / 2, topPx + heightPx)
|
|
883
874
|
} else {
|
|
884
875
|
const padding = 2
|
|
885
876
|
ctx.fillStyle = 'purple'
|
|
@@ -961,7 +952,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
961
952
|
}
|
|
962
953
|
}
|
|
963
954
|
|
|
964
|
-
|
|
955
|
+
makeImageData(
|
|
965
956
|
ctx: CanvasRenderingContext2D,
|
|
966
957
|
layoutRecords: (LayoutFeature | null)[],
|
|
967
958
|
props: RenderArgsDeserializedWithFeaturesAndLayout,
|
|
@@ -969,7 +960,10 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
969
960
|
const { layout, config, showSoftClip, colorBy, theme: configTheme } = props
|
|
970
961
|
const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
|
|
971
962
|
const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
|
|
972
|
-
const
|
|
963
|
+
const largeInsertionIndicatorScale = readConfObject(
|
|
964
|
+
config,
|
|
965
|
+
'largeInsertionIndicatorScale',
|
|
966
|
+
)
|
|
973
967
|
const defaultColor = readConfObject(config, 'color') === '#f0f'
|
|
974
968
|
|
|
975
969
|
const theme = createJBrowseTheme(configTheme)
|
|
@@ -984,9 +978,12 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
984
978
|
ctx.font = 'bold 10px Courier New,monospace'
|
|
985
979
|
|
|
986
980
|
const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
|
|
987
|
-
|
|
981
|
+
const drawSNPs = shouldDrawSNPs(colorBy?.type)
|
|
982
|
+
const drawIndels = shouldDrawIndels(colorBy?.type)
|
|
983
|
+
for (let i = 0; i < layoutRecords.length; i++) {
|
|
984
|
+
const feat = layoutRecords[i]
|
|
988
985
|
if (feat === null) {
|
|
989
|
-
|
|
986
|
+
continue
|
|
990
987
|
}
|
|
991
988
|
|
|
992
989
|
this.drawAlignmentRect(ctx, feat, {
|
|
@@ -999,19 +996,25 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
999
996
|
})
|
|
1000
997
|
this.drawMismatches(ctx, feat, props, {
|
|
1001
998
|
mismatchAlpha,
|
|
1002
|
-
drawSNPs
|
|
1003
|
-
drawIndels
|
|
1004
|
-
largeInsertionIndicatorScale
|
|
999
|
+
drawSNPs,
|
|
1000
|
+
drawIndels,
|
|
1001
|
+
largeInsertionIndicatorScale,
|
|
1005
1002
|
minSubfeatureWidth,
|
|
1006
1003
|
charWidth,
|
|
1007
1004
|
charHeight,
|
|
1008
1005
|
colorForBase,
|
|
1009
1006
|
contrastForBase,
|
|
1010
1007
|
})
|
|
1011
|
-
|
|
1008
|
+
}
|
|
1009
|
+
if (showSoftClip) {
|
|
1010
|
+
for (let i = 0; i < layoutRecords.length; i++) {
|
|
1011
|
+
const feat = layoutRecords[i]
|
|
1012
|
+
if (feat === null) {
|
|
1013
|
+
continue
|
|
1014
|
+
}
|
|
1012
1015
|
this.drawSoftClipping(ctx, feat, props, config, theme)
|
|
1013
1016
|
}
|
|
1014
|
-
}
|
|
1017
|
+
}
|
|
1015
1018
|
}
|
|
1016
1019
|
|
|
1017
1020
|
// we perform a full layout before render as a separate method because the
|
|
@@ -1041,7 +1044,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
1041
1044
|
|
|
1042
1045
|
const heightPx = readConfObject(config, 'height')
|
|
1043
1046
|
const displayMode = readConfObject(config, 'displayMode')
|
|
1044
|
-
|
|
1047
|
+
return iterMap(
|
|
1045
1048
|
featureMap.values(),
|
|
1046
1049
|
feature =>
|
|
1047
1050
|
this.layoutFeature({
|
|
@@ -1055,7 +1058,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
1055
1058
|
}),
|
|
1056
1059
|
featureMap.size,
|
|
1057
1060
|
)
|
|
1058
|
-
return layoutRecords
|
|
1059
1061
|
}
|
|
1060
1062
|
|
|
1061
1063
|
async fetchSequence(renderProps: RenderArgsDeserialized) {
|
|
@@ -31,8 +31,8 @@ export default ConfigurationSchema(
|
|
|
31
31
|
minSubfeatureWidth: {
|
|
32
32
|
type: 'number',
|
|
33
33
|
description:
|
|
34
|
-
'the minimum width in px for a pileup mismatch feature. use for increasing mismatch marker widths when zoomed out
|
|
35
|
-
defaultValue: 0,
|
|
34
|
+
'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',
|
|
35
|
+
defaultValue: 0.7,
|
|
36
36
|
},
|
|
37
37
|
maxHeight: {
|
|
38
38
|
type: 'integer',
|