@jbrowse/plugin-alignments 1.7.7 → 1.7.10
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 +13 -3
- package/dist/AlignmentsFeatureDetail/index.d.ts +28 -3
- package/dist/AlignmentsFeatureDetail/index.js +6 -17
- 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 +108 -46
- 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.d.ts +6 -3
- package/dist/LinearPileupDisplay/model.js +132 -51
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +37 -17
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
- 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 +66 -9
- package/dist/PileupRenderer/PileupRenderer.js +296 -258
- package/dist/PileupRenderer/configSchema.js +2 -2
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +6 -6
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +95 -96
- package/dist/SNPCoverageRenderer/configSchema.d.ts +1 -1
- package/package.json +3 -3
- package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +14 -3
- package/src/AlignmentsFeatureDetail/index.ts +7 -17
- package/src/BamAdapter/BamAdapter.ts +3 -3
- package/src/BamAdapter/MismatchParser.test.ts +5 -7
- package/src/BamAdapter/MismatchParser.ts +72 -59
- package/src/CramAdapter/CramAdapter.ts +14 -10
- package/src/CramAdapter/CramSlightlyLazyFeature.ts +84 -91
- package/src/LinearPileupDisplay/model.ts +76 -24
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +41 -20
- package/src/LinearSNPCoverageDisplay/models/model.ts +1 -1
- package/src/PileupRenderer/PileupLayoutSession.ts +6 -1
- package/src/PileupRenderer/PileupRenderer.tsx +413 -225
- package/src/PileupRenderer/configSchema.ts +2 -2
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +89 -76
|
@@ -44,6 +44,25 @@ import {
|
|
|
44
44
|
} from './PileupLayoutSession'
|
|
45
45
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
46
46
|
|
|
47
|
+
function fillRect(
|
|
48
|
+
ctx: CanvasRenderingContext2D,
|
|
49
|
+
l: number,
|
|
50
|
+
t: number,
|
|
51
|
+
w: number,
|
|
52
|
+
h: number,
|
|
53
|
+
cw: number,
|
|
54
|
+
color?: string,
|
|
55
|
+
) {
|
|
56
|
+
if (l + w < 0 || l > cw) {
|
|
57
|
+
return
|
|
58
|
+
} else {
|
|
59
|
+
if (color) {
|
|
60
|
+
ctx.fillStyle = color
|
|
61
|
+
}
|
|
62
|
+
ctx.fillRect(l, t, w, h)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
function getColorBaseMap(theme: Theme) {
|
|
48
67
|
return {
|
|
49
68
|
A: theme.palette.bases.A.main,
|
|
@@ -118,18 +137,22 @@ interface LayoutFeature {
|
|
|
118
137
|
feature: Feature
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
function
|
|
140
|
+
function shouldDrawSNPs(type?: string) {
|
|
122
141
|
return !['methylation', 'modifications'].includes(type || '')
|
|
123
142
|
}
|
|
124
143
|
|
|
144
|
+
function shouldDrawIndels(type?: string) {
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
125
148
|
export default class PileupRenderer extends BoxRendererType {
|
|
126
149
|
supportsSVG = true
|
|
127
150
|
|
|
128
151
|
// get width and height of chars the height is an approximation: width
|
|
129
152
|
// letter M is approximately the height
|
|
130
153
|
getCharWidthHeight(ctx: CanvasRenderingContext2D) {
|
|
131
|
-
const charWidth =
|
|
132
|
-
const charHeight =
|
|
154
|
+
const charWidth = measureText('A')
|
|
155
|
+
const charHeight = measureText('M')
|
|
133
156
|
return { charWidth, charHeight }
|
|
134
157
|
}
|
|
135
158
|
|
|
@@ -283,25 +306,32 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
283
306
|
return strand === 1 ? 'color_fwd_strand' : 'color_rev_strand'
|
|
284
307
|
}
|
|
285
308
|
|
|
286
|
-
colorByPerBaseLettering(
|
|
287
|
-
ctx
|
|
288
|
-
feat
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
309
|
+
colorByPerBaseLettering({
|
|
310
|
+
ctx,
|
|
311
|
+
feat,
|
|
312
|
+
region,
|
|
313
|
+
bpPerPx,
|
|
314
|
+
colorForBase,
|
|
315
|
+
contrastForBase,
|
|
316
|
+
charWidth,
|
|
317
|
+
charHeight,
|
|
318
|
+
canvasWidth,
|
|
319
|
+
}: {
|
|
320
|
+
ctx: CanvasRenderingContext2D
|
|
321
|
+
feat: LayoutFeature
|
|
322
|
+
region: Region
|
|
323
|
+
bpPerPx: number
|
|
324
|
+
colorForBase: Record<string, string>
|
|
325
|
+
contrastForBase: Record<string, string>
|
|
326
|
+
charWidth: number
|
|
327
|
+
charHeight: number
|
|
328
|
+
canvasWidth: number
|
|
329
|
+
}) {
|
|
300
330
|
const heightLim = charHeight - 2
|
|
301
331
|
const { feature, topPx, heightPx } = feat
|
|
302
332
|
const seq = feature.get('seq') as string
|
|
303
333
|
const cigarOps = parseCigar(feature.get('CIGAR'))
|
|
304
|
-
const
|
|
334
|
+
const w = 1 / bpPerPx
|
|
305
335
|
const start = feature.get('start')
|
|
306
336
|
let soffset = 0 // sequence offset
|
|
307
337
|
let roffset = 0 // reference offset
|
|
@@ -316,22 +346,25 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
316
346
|
} else if (op === 'M' || op === 'X' || op === '=') {
|
|
317
347
|
for (let m = 0; m < len; m++) {
|
|
318
348
|
const letter = seq[soffset + m]
|
|
319
|
-
|
|
320
|
-
const [leftPx] = bpSpanPx(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
349
|
+
const r = start + roffset + m
|
|
350
|
+
const [leftPx] = bpSpanPx(r, r + 1, region, bpPerPx)
|
|
351
|
+
fillRect(
|
|
352
|
+
ctx,
|
|
353
|
+
leftPx,
|
|
354
|
+
topPx,
|
|
355
|
+
w + 0.5,
|
|
356
|
+
heightPx,
|
|
357
|
+
canvasWidth,
|
|
358
|
+
colorForBase[letter],
|
|
325
359
|
)
|
|
326
|
-
ctx.fillRect(leftPx, topPx, widthPx + 0.5, heightPx)
|
|
327
360
|
|
|
328
|
-
if (
|
|
361
|
+
if (w >= charWidth && heightPx >= heightLim) {
|
|
329
362
|
// normal SNP coloring
|
|
330
363
|
ctx.fillStyle = contrastForBase[letter]
|
|
331
364
|
|
|
332
365
|
ctx.fillText(
|
|
333
366
|
letter,
|
|
334
|
-
leftPx + (
|
|
367
|
+
leftPx + (w - charWidth) / 2 + 1,
|
|
335
368
|
topPx + heightPx,
|
|
336
369
|
)
|
|
337
370
|
}
|
|
@@ -341,13 +374,19 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
341
374
|
}
|
|
342
375
|
}
|
|
343
376
|
}
|
|
344
|
-
colorByPerBaseQuality(
|
|
345
|
-
ctx
|
|
346
|
-
feat
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
377
|
+
colorByPerBaseQuality({
|
|
378
|
+
ctx,
|
|
379
|
+
feat,
|
|
380
|
+
region,
|
|
381
|
+
bpPerPx,
|
|
382
|
+
canvasWidth,
|
|
383
|
+
}: {
|
|
384
|
+
ctx: CanvasRenderingContext2D
|
|
385
|
+
feat: LayoutFeature
|
|
386
|
+
region: Region
|
|
387
|
+
bpPerPx: number
|
|
388
|
+
canvasWidth: number
|
|
389
|
+
}) {
|
|
351
390
|
const { feature, topPx, heightPx } = feat
|
|
352
391
|
const qual: string = feature.get('qual') || ''
|
|
353
392
|
const scores = qual.split(' ').map(val => +val)
|
|
@@ -367,14 +406,21 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
367
406
|
} else if (op === 'M' || op === 'X' || op === '=') {
|
|
368
407
|
for (let m = 0; m < len; m++) {
|
|
369
408
|
const score = scores[soffset + m]
|
|
370
|
-
ctx.fillStyle = `hsl(${score === 255 ? 150 : score * 1.5},55%,50%)`
|
|
371
409
|
const [leftPx] = bpSpanPx(
|
|
372
410
|
start + roffset + m,
|
|
373
411
|
start + roffset + m + 1,
|
|
374
412
|
region,
|
|
375
413
|
bpPerPx,
|
|
376
414
|
)
|
|
377
|
-
|
|
415
|
+
fillRect(
|
|
416
|
+
ctx,
|
|
417
|
+
leftPx,
|
|
418
|
+
topPx,
|
|
419
|
+
width + 0.5,
|
|
420
|
+
heightPx,
|
|
421
|
+
canvasWidth,
|
|
422
|
+
`hsl(${score === 255 ? 150 : score * 1.5},55%,50%)`,
|
|
423
|
+
)
|
|
378
424
|
}
|
|
379
425
|
soffset += len
|
|
380
426
|
roffset += len
|
|
@@ -391,16 +437,23 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
391
437
|
// has very high likelihood basecalls at that point, we really only care
|
|
392
438
|
// about low qual calls <20 approx
|
|
393
439
|
//
|
|
394
|
-
colorByModifications(
|
|
395
|
-
ctx
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
440
|
+
colorByModifications({
|
|
441
|
+
ctx,
|
|
442
|
+
feat,
|
|
443
|
+
region,
|
|
444
|
+
bpPerPx,
|
|
445
|
+
renderArgs,
|
|
446
|
+
canvasWidth,
|
|
447
|
+
}: {
|
|
448
|
+
ctx: CanvasRenderingContext2D
|
|
449
|
+
feat: LayoutFeature
|
|
450
|
+
region: Region
|
|
451
|
+
bpPerPx: number
|
|
452
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
453
|
+
canvasWidth: number
|
|
454
|
+
}) {
|
|
455
|
+
const { feature, topPx, heightPx } = feat
|
|
456
|
+
const { modificationTagMap = {} } = renderArgs
|
|
404
457
|
|
|
405
458
|
const mm = (getTagAlt(feature, 'MM', 'Mm') as string) || ''
|
|
406
459
|
|
|
@@ -417,7 +470,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
417
470
|
|
|
418
471
|
const cigar = feature.get('CIGAR')
|
|
419
472
|
const start = feature.get('start')
|
|
420
|
-
const end = feature.get('end')
|
|
421
473
|
const seq = feature.get('seq')
|
|
422
474
|
const strand = feature.get('strand')
|
|
423
475
|
const cigarOps = parseCigar(cigar)
|
|
@@ -431,22 +483,27 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
431
483
|
const col = modificationTagMap[type] || 'black'
|
|
432
484
|
const base = Color(col)
|
|
433
485
|
for (const readPos of getNextRefPos(cigarOps, positions)) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
486
|
+
const r = start + readPos
|
|
487
|
+
const [leftPx, rightPx] = bpSpanPx(r, r + 1, region, bpPerPx)
|
|
488
|
+
|
|
489
|
+
// give it a little boost of 0.1 to not make them fully
|
|
490
|
+
// invisible to avoid confusion
|
|
491
|
+
const prob = probabilities[probIndex]
|
|
492
|
+
|
|
493
|
+
fillRect(
|
|
494
|
+
ctx,
|
|
495
|
+
leftPx,
|
|
496
|
+
topPx,
|
|
497
|
+
rightPx - leftPx + 0.5,
|
|
498
|
+
heightPx,
|
|
499
|
+
canvasWidth,
|
|
500
|
+
prob && prob !== 1
|
|
501
|
+
? base
|
|
502
|
+
.alpha(prob + 0.1)
|
|
503
|
+
.hsl()
|
|
504
|
+
.string()
|
|
505
|
+
: col,
|
|
506
|
+
)
|
|
450
507
|
probIndex++
|
|
451
508
|
}
|
|
452
509
|
}
|
|
@@ -455,16 +512,23 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
455
512
|
// Color by methylation is slightly modified version of color by
|
|
456
513
|
// modifications that focuses on CpG sites, with non-methylated CpG colored
|
|
457
514
|
// blue
|
|
458
|
-
colorByMethylation(
|
|
459
|
-
ctx
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
515
|
+
colorByMethylation({
|
|
516
|
+
ctx,
|
|
517
|
+
feat,
|
|
518
|
+
region,
|
|
519
|
+
bpPerPx,
|
|
520
|
+
renderArgs,
|
|
521
|
+
canvasWidth,
|
|
522
|
+
}: {
|
|
523
|
+
ctx: CanvasRenderingContext2D
|
|
524
|
+
feat: LayoutFeature
|
|
525
|
+
region: Region
|
|
526
|
+
bpPerPx: number
|
|
527
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
528
|
+
canvasWidth: number
|
|
529
|
+
}) {
|
|
530
|
+
const { regionSequence } = renderArgs
|
|
531
|
+
const { feature, topPx, heightPx } = feat
|
|
468
532
|
|
|
469
533
|
const mm: string = getTagAlt(feature, 'MM', 'Mm') || ''
|
|
470
534
|
|
|
@@ -478,15 +542,14 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
478
542
|
const seq = feature.get('seq')
|
|
479
543
|
const strand = feature.get('strand')
|
|
480
544
|
const cigarOps = parseCigar(cigar)
|
|
481
|
-
const { start: rstart, end: rend } = region
|
|
482
545
|
|
|
483
|
-
const methBins = new Array(
|
|
546
|
+
const methBins = new Array(region.end - region.start).fill(0)
|
|
484
547
|
const modifications = getModificationPositions(mm, seq, strand)
|
|
485
548
|
for (let i = 0; i < modifications.length; i++) {
|
|
486
549
|
const { type, positions } = modifications[i]
|
|
487
550
|
if (type === 'm' && positions) {
|
|
488
551
|
for (const pos of getNextRefPos(cigarOps, positions)) {
|
|
489
|
-
const epos = pos + fstart -
|
|
552
|
+
const epos = pos + fstart - region.start
|
|
490
553
|
if (epos >= 0 && epos < methBins.length) {
|
|
491
554
|
methBins[epos] = 1
|
|
492
555
|
}
|
|
@@ -495,7 +558,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
495
558
|
}
|
|
496
559
|
|
|
497
560
|
for (let j = fstart; j < fend; j++) {
|
|
498
|
-
const i = j -
|
|
561
|
+
const i = j - region.start
|
|
499
562
|
if (i >= 0 && i < methBins.length) {
|
|
500
563
|
const l1 = regionSequence[i].toLowerCase()
|
|
501
564
|
const l2 = regionSequence[i + 1].toLowerCase()
|
|
@@ -503,36 +566,45 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
503
566
|
// if we are zoomed out, display just a block over the cpg
|
|
504
567
|
if (bpPerPx > 2) {
|
|
505
568
|
if (l1 === 'c' && l2 === 'g') {
|
|
506
|
-
const s =
|
|
569
|
+
const s = region.start + i
|
|
507
570
|
const [leftPx, rightPx] = bpSpanPx(s, s + 2, region, bpPerPx)
|
|
508
|
-
|
|
509
|
-
ctx
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
571
|
+
fillRect(
|
|
572
|
+
ctx,
|
|
573
|
+
leftPx,
|
|
574
|
+
topPx,
|
|
575
|
+
rightPx - leftPx + 0.5,
|
|
576
|
+
heightPx,
|
|
577
|
+
canvasWidth,
|
|
578
|
+
methBins[i] || methBins[i + 1] ? 'red' : 'blue',
|
|
579
|
+
)
|
|
514
580
|
}
|
|
515
581
|
}
|
|
516
582
|
// if we are zoomed in, color the c inside the cpg
|
|
517
583
|
else {
|
|
518
584
|
// color
|
|
519
585
|
if (l1 === 'c' && l2 === 'g') {
|
|
520
|
-
const s =
|
|
586
|
+
const s = region.start + i
|
|
521
587
|
const [leftPx, rightPx] = bpSpanPx(s, s + 1, region, bpPerPx)
|
|
522
|
-
|
|
523
|
-
ctx
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
588
|
+
fillRect(
|
|
589
|
+
ctx,
|
|
590
|
+
leftPx,
|
|
591
|
+
topPx,
|
|
592
|
+
rightPx - leftPx + 0.5,
|
|
593
|
+
heightPx,
|
|
594
|
+
canvasWidth,
|
|
595
|
+
methBins[i] ? 'red' : 'blue',
|
|
596
|
+
)
|
|
528
597
|
|
|
529
598
|
const [leftPx2, rightPx2] = bpSpanPx(s + 1, s + 2, region, bpPerPx)
|
|
530
|
-
|
|
531
|
-
ctx
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
599
|
+
fillRect(
|
|
600
|
+
ctx,
|
|
601
|
+
leftPx2,
|
|
602
|
+
topPx,
|
|
603
|
+
rightPx2 - leftPx2 + 0.5,
|
|
604
|
+
heightPx,
|
|
605
|
+
canvasWidth,
|
|
606
|
+
methBins[i + 1] ? 'red' : 'blue',
|
|
607
|
+
)
|
|
536
608
|
}
|
|
537
609
|
}
|
|
538
610
|
}
|
|
@@ -580,29 +652,29 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
580
652
|
}
|
|
581
653
|
}
|
|
582
654
|
|
|
583
|
-
drawAlignmentRect(
|
|
584
|
-
ctx
|
|
585
|
-
feat
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
655
|
+
drawAlignmentRect({
|
|
656
|
+
ctx,
|
|
657
|
+
feat,
|
|
658
|
+
renderArgs,
|
|
659
|
+
colorForBase,
|
|
660
|
+
contrastForBase,
|
|
661
|
+
charWidth,
|
|
662
|
+
charHeight,
|
|
663
|
+
defaultColor,
|
|
664
|
+
canvasWidth,
|
|
665
|
+
}: {
|
|
666
|
+
ctx: CanvasRenderingContext2D
|
|
667
|
+
feat: LayoutFeature
|
|
668
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
669
|
+
colorForBase: Record<string, string>
|
|
670
|
+
contrastForBase: Record<string, string>
|
|
671
|
+
charWidth: number
|
|
672
|
+
charHeight: number
|
|
673
|
+
defaultColor: boolean
|
|
674
|
+
canvasWidth: number
|
|
675
|
+
}) {
|
|
676
|
+
const { config, bpPerPx, regions, colorBy, colorTagMap = {} } = renderArgs
|
|
677
|
+
|
|
606
678
|
const { tag = '', type: colorType = '' } = colorBy || {}
|
|
607
679
|
const { feature } = feat
|
|
608
680
|
const region = regions[0]
|
|
@@ -689,62 +761,89 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
689
761
|
break
|
|
690
762
|
}
|
|
691
763
|
|
|
692
|
-
this.drawRect(ctx, feat,
|
|
764
|
+
this.drawRect(ctx, feat, renderArgs)
|
|
693
765
|
|
|
694
766
|
// second pass for color types that render per-base things that go over the
|
|
695
767
|
// existing drawing
|
|
696
768
|
switch (colorType) {
|
|
697
769
|
case 'perBaseQuality':
|
|
698
|
-
this.colorByPerBaseQuality(
|
|
770
|
+
this.colorByPerBaseQuality({
|
|
771
|
+
ctx,
|
|
772
|
+
feat,
|
|
773
|
+
region,
|
|
774
|
+
bpPerPx,
|
|
775
|
+
canvasWidth,
|
|
776
|
+
})
|
|
699
777
|
break
|
|
700
778
|
|
|
701
779
|
case 'perBaseLettering':
|
|
702
|
-
this.colorByPerBaseLettering(
|
|
780
|
+
this.colorByPerBaseLettering({
|
|
781
|
+
ctx,
|
|
782
|
+
feat,
|
|
783
|
+
region,
|
|
784
|
+
bpPerPx,
|
|
703
785
|
colorForBase,
|
|
704
786
|
contrastForBase,
|
|
705
787
|
charWidth,
|
|
706
788
|
charHeight,
|
|
789
|
+
canvasWidth,
|
|
707
790
|
})
|
|
708
791
|
break
|
|
709
792
|
|
|
710
793
|
case 'modifications':
|
|
711
|
-
this.colorByModifications(
|
|
794
|
+
this.colorByModifications({
|
|
795
|
+
ctx,
|
|
796
|
+
feat,
|
|
797
|
+
region,
|
|
798
|
+
bpPerPx,
|
|
799
|
+
renderArgs,
|
|
800
|
+
canvasWidth,
|
|
801
|
+
})
|
|
712
802
|
break
|
|
713
803
|
|
|
714
804
|
case 'methylation':
|
|
715
|
-
this.colorByMethylation(
|
|
805
|
+
this.colorByMethylation({
|
|
806
|
+
ctx,
|
|
807
|
+
feat,
|
|
808
|
+
region,
|
|
809
|
+
bpPerPx,
|
|
810
|
+
renderArgs,
|
|
811
|
+
canvasWidth,
|
|
812
|
+
})
|
|
716
813
|
break
|
|
717
814
|
}
|
|
718
815
|
}
|
|
719
816
|
|
|
720
|
-
drawMismatches(
|
|
721
|
-
ctx
|
|
722
|
-
feat
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
817
|
+
drawMismatches({
|
|
818
|
+
ctx,
|
|
819
|
+
feat,
|
|
820
|
+
renderArgs,
|
|
821
|
+
minSubfeatureWidth,
|
|
822
|
+
largeInsertionIndicatorScale,
|
|
823
|
+
mismatchAlpha,
|
|
824
|
+
charWidth,
|
|
825
|
+
charHeight,
|
|
826
|
+
colorForBase,
|
|
827
|
+
contrastForBase,
|
|
828
|
+
canvasWidth,
|
|
829
|
+
drawSNPs = true,
|
|
830
|
+
drawIndels = true,
|
|
831
|
+
}: {
|
|
832
|
+
ctx: CanvasRenderingContext2D
|
|
833
|
+
feat: LayoutFeature
|
|
834
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
835
|
+
colorForBase: { [key: string]: string }
|
|
836
|
+
contrastForBase: { [key: string]: string }
|
|
837
|
+
mismatchAlpha?: boolean
|
|
838
|
+
drawSNPs?: boolean
|
|
839
|
+
drawIndels?: boolean
|
|
840
|
+
minSubfeatureWidth: number
|
|
841
|
+
largeInsertionIndicatorScale: number
|
|
842
|
+
charWidth: number
|
|
843
|
+
charHeight: number
|
|
844
|
+
canvasWidth: number
|
|
845
|
+
}) {
|
|
846
|
+
const { bpPerPx, regions } = renderArgs
|
|
748
847
|
const { heightPx, topPx, feature } = feat
|
|
749
848
|
const [region] = regions
|
|
750
849
|
const start = feature.get('start')
|
|
@@ -754,17 +853,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
754
853
|
const mismatches: Mismatch[] = feature.get('mismatches')
|
|
755
854
|
const heightLim = charHeight - 2
|
|
756
855
|
|
|
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
856
|
// extraHorizontallyFlippedOffset is used to draw interbase items, which
|
|
769
857
|
// are located to the left when forward and right when reversed
|
|
770
858
|
const extraHorizontallyFlippedOffset = region.reversed
|
|
@@ -783,14 +871,34 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
783
871
|
if (mismatch.type === 'mismatch' && drawSNPs) {
|
|
784
872
|
const baseColor = colorForBase[mismatch.base] || '#888'
|
|
785
873
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
874
|
+
fillRect(
|
|
875
|
+
ctx,
|
|
876
|
+
leftPx,
|
|
877
|
+
topPx,
|
|
878
|
+
widthPx,
|
|
879
|
+
heightPx,
|
|
880
|
+
canvasWidth,
|
|
881
|
+
!mismatchAlpha
|
|
882
|
+
? baseColor
|
|
883
|
+
: mismatch.qual !== undefined
|
|
884
|
+
? Color(baseColor)
|
|
885
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
886
|
+
.hsl()
|
|
887
|
+
.string()
|
|
888
|
+
: baseColor,
|
|
889
|
+
)
|
|
789
890
|
|
|
790
891
|
if (widthPx >= charWidth && heightPx >= heightLim) {
|
|
791
892
|
// normal SNP coloring
|
|
792
|
-
const
|
|
793
|
-
ctx.fillStyle =
|
|
893
|
+
const contrastColor = contrastForBase[mismatch.base] || 'black'
|
|
894
|
+
ctx.fillStyle = !mismatchAlpha
|
|
895
|
+
? contrastColor
|
|
896
|
+
: mismatch.qual !== undefined
|
|
897
|
+
? Color(contrastColor)
|
|
898
|
+
.alpha(Math.min(1, mismatch.qual / 50))
|
|
899
|
+
.hsl()
|
|
900
|
+
.string()
|
|
901
|
+
: contrastColor
|
|
794
902
|
ctx.fillText(
|
|
795
903
|
mbase,
|
|
796
904
|
leftPx + (widthPx - charWidth) / 2 + 1,
|
|
@@ -798,9 +906,15 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
798
906
|
)
|
|
799
907
|
}
|
|
800
908
|
} else if (mismatch.type === 'deletion' && drawIndels) {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
909
|
+
fillRect(
|
|
910
|
+
ctx,
|
|
911
|
+
leftPx,
|
|
912
|
+
topPx,
|
|
913
|
+
widthPx,
|
|
914
|
+
heightPx,
|
|
915
|
+
canvasWidth,
|
|
916
|
+
colorForBase.deletion,
|
|
917
|
+
)
|
|
804
918
|
const txt = `${mismatch.length}`
|
|
805
919
|
const rwidth = measureText(txt, 10)
|
|
806
920
|
if (widthPx >= rwidth && heightPx >= heightLim) {
|
|
@@ -820,20 +934,34 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
820
934
|
Math.min(1.2, 1 / bpPerPx),
|
|
821
935
|
)
|
|
822
936
|
if (len < 10) {
|
|
823
|
-
|
|
937
|
+
fillRect(ctx, pos, topPx, insW, heightPx, canvasWidth, 'purple')
|
|
824
938
|
if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
|
|
825
|
-
|
|
826
|
-
|
|
939
|
+
fillRect(ctx, pos - insW, topPx, insW * 3, 1, canvasWidth)
|
|
940
|
+
fillRect(
|
|
941
|
+
ctx,
|
|
942
|
+
pos - insW,
|
|
943
|
+
topPx + heightPx - 1,
|
|
944
|
+
insW * 3,
|
|
945
|
+
1,
|
|
946
|
+
canvasWidth,
|
|
947
|
+
)
|
|
827
948
|
ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
|
|
828
949
|
}
|
|
829
950
|
}
|
|
830
951
|
} else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
|
|
831
|
-
ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
|
|
832
952
|
const pos = leftPx + extraHorizontallyFlippedOffset
|
|
833
|
-
|
|
953
|
+
fillRect(
|
|
954
|
+
ctx,
|
|
955
|
+
pos,
|
|
956
|
+
topPx,
|
|
957
|
+
w,
|
|
958
|
+
heightPx,
|
|
959
|
+
canvasWidth,
|
|
960
|
+
mismatch.type === 'hardclip' ? 'red' : 'blue',
|
|
961
|
+
)
|
|
834
962
|
if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
|
|
835
|
-
|
|
836
|
-
|
|
963
|
+
fillRect(ctx, pos - w, topPx, w * 3, 1, canvasWidth)
|
|
964
|
+
fillRect(ctx, pos - w, topPx + heightPx - 1, w * 3, 1, canvasWidth)
|
|
837
965
|
ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
|
|
838
966
|
}
|
|
839
967
|
} else if (mismatch.type === 'skip') {
|
|
@@ -844,12 +972,14 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
844
972
|
// make small exons more visible when zoomed far out
|
|
845
973
|
const adjustPx = widthPx - (bpPerPx > 10 ? 1.5 : 0)
|
|
846
974
|
ctx.clearRect(leftPx, topPx, adjustPx, heightPx)
|
|
847
|
-
|
|
848
|
-
|
|
975
|
+
fillRect(
|
|
976
|
+
ctx,
|
|
849
977
|
Math.max(0, leftPx),
|
|
850
978
|
topPx + heightPx / 2 - 1,
|
|
851
979
|
adjustPx + (leftPx < 0 ? leftPx : 0),
|
|
852
980
|
2,
|
|
981
|
+
canvasWidth,
|
|
982
|
+
'#333',
|
|
853
983
|
)
|
|
854
984
|
}
|
|
855
985
|
}
|
|
@@ -866,39 +996,55 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
866
996
|
const txt = `${len}`
|
|
867
997
|
if (mismatch.type === 'insertion' && len >= 10) {
|
|
868
998
|
if (bpPerPx > largeInsertionIndicatorScale) {
|
|
869
|
-
ctx
|
|
870
|
-
ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
|
|
999
|
+
fillRect(ctx, leftPx - 1, topPx, 2, heightPx, canvasWidth, 'purple')
|
|
871
1000
|
} else if (heightPx > charHeight) {
|
|
872
|
-
const
|
|
1001
|
+
const rwidth = measureText(txt)
|
|
873
1002
|
const padding = 5
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
leftPx -
|
|
1003
|
+
fillRect(
|
|
1004
|
+
ctx,
|
|
1005
|
+
leftPx - rwidth / 2 - padding,
|
|
877
1006
|
topPx,
|
|
878
|
-
|
|
1007
|
+
rwidth + 2 * padding,
|
|
879
1008
|
heightPx,
|
|
1009
|
+
canvasWidth,
|
|
1010
|
+
'purple',
|
|
880
1011
|
)
|
|
881
1012
|
ctx.fillStyle = 'white'
|
|
882
|
-
ctx.fillText(txt, leftPx -
|
|
1013
|
+
ctx.fillText(txt, leftPx - rwidth / 2, topPx + heightPx)
|
|
883
1014
|
} else {
|
|
884
1015
|
const padding = 2
|
|
885
|
-
|
|
886
|
-
|
|
1016
|
+
fillRect(
|
|
1017
|
+
ctx,
|
|
1018
|
+
leftPx - padding,
|
|
1019
|
+
topPx,
|
|
1020
|
+
2 * padding,
|
|
1021
|
+
heightPx,
|
|
1022
|
+
canvasWidth,
|
|
1023
|
+
'purple',
|
|
1024
|
+
)
|
|
887
1025
|
}
|
|
888
1026
|
}
|
|
889
1027
|
}
|
|
890
1028
|
}
|
|
891
1029
|
}
|
|
892
1030
|
|
|
893
|
-
drawSoftClipping(
|
|
894
|
-
ctx
|
|
895
|
-
feat
|
|
896
|
-
|
|
897
|
-
config
|
|
898
|
-
theme
|
|
899
|
-
|
|
1031
|
+
drawSoftClipping({
|
|
1032
|
+
ctx,
|
|
1033
|
+
feat,
|
|
1034
|
+
renderArgs,
|
|
1035
|
+
config,
|
|
1036
|
+
theme,
|
|
1037
|
+
canvasWidth,
|
|
1038
|
+
}: {
|
|
1039
|
+
ctx: CanvasRenderingContext2D
|
|
1040
|
+
feat: LayoutFeature
|
|
1041
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
1042
|
+
config: AnyConfigurationModel
|
|
1043
|
+
theme: Theme
|
|
1044
|
+
canvasWidth: number
|
|
1045
|
+
}) {
|
|
900
1046
|
const { feature, topPx, heightPx } = feat
|
|
901
|
-
const { regions, bpPerPx } =
|
|
1047
|
+
const { regions, bpPerPx } = renderArgs
|
|
902
1048
|
const [region] = regions
|
|
903
1049
|
const minFeatWidth = readConfObject(config, 'minSubfeatureWidth')
|
|
904
1050
|
const mismatches: Mismatch[] = feature.get('mismatches')
|
|
@@ -946,7 +1092,14 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
946
1092
|
// show in soft clipping
|
|
947
1093
|
const baseColor = colorForBase[base] || '#000000'
|
|
948
1094
|
ctx.fillStyle = baseColor
|
|
949
|
-
|
|
1095
|
+
fillRect(
|
|
1096
|
+
ctx,
|
|
1097
|
+
softClipLeftPx,
|
|
1098
|
+
topPx,
|
|
1099
|
+
softClipWidthPx,
|
|
1100
|
+
heightPx,
|
|
1101
|
+
canvasWidth,
|
|
1102
|
+
)
|
|
950
1103
|
|
|
951
1104
|
if (softClipWidthPx >= charWidth && heightPx >= charHeight - 5) {
|
|
952
1105
|
ctx.fillStyle = theme.palette.getContrastText(baseColor)
|
|
@@ -961,15 +1114,30 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
961
1114
|
}
|
|
962
1115
|
}
|
|
963
1116
|
|
|
964
|
-
|
|
965
|
-
ctx
|
|
966
|
-
layoutRecords
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1117
|
+
makeImageData({
|
|
1118
|
+
ctx,
|
|
1119
|
+
layoutRecords,
|
|
1120
|
+
canvasWidth,
|
|
1121
|
+
renderArgs,
|
|
1122
|
+
}: {
|
|
1123
|
+
ctx: CanvasRenderingContext2D
|
|
1124
|
+
canvasWidth: number
|
|
1125
|
+
layoutRecords: (LayoutFeature | null)[]
|
|
1126
|
+
renderArgs: RenderArgsDeserializedWithFeaturesAndLayout
|
|
1127
|
+
}) {
|
|
1128
|
+
const {
|
|
1129
|
+
layout,
|
|
1130
|
+
config,
|
|
1131
|
+
showSoftClip,
|
|
1132
|
+
colorBy,
|
|
1133
|
+
theme: configTheme,
|
|
1134
|
+
} = renderArgs
|
|
970
1135
|
const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
|
|
971
1136
|
const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
|
|
972
|
-
const
|
|
1137
|
+
const largeInsertionIndicatorScale = readConfObject(
|
|
1138
|
+
config,
|
|
1139
|
+
'largeInsertionIndicatorScale',
|
|
1140
|
+
)
|
|
973
1141
|
const defaultColor = readConfObject(config, 'color') === '#f0f'
|
|
974
1142
|
|
|
975
1143
|
const theme = createJBrowseTheme(configTheme)
|
|
@@ -984,34 +1152,51 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
984
1152
|
ctx.font = 'bold 10px Courier New,monospace'
|
|
985
1153
|
|
|
986
1154
|
const { charWidth, charHeight } = this.getCharWidthHeight(ctx)
|
|
987
|
-
|
|
1155
|
+
const drawSNPs = shouldDrawSNPs(colorBy?.type)
|
|
1156
|
+
const drawIndels = shouldDrawIndels(colorBy?.type)
|
|
1157
|
+
for (let i = 0; i < layoutRecords.length; i++) {
|
|
1158
|
+
const feat = layoutRecords[i]
|
|
988
1159
|
if (feat === null) {
|
|
989
|
-
|
|
1160
|
+
continue
|
|
990
1161
|
}
|
|
991
1162
|
|
|
992
|
-
this.drawAlignmentRect(
|
|
993
|
-
|
|
1163
|
+
this.drawAlignmentRect({
|
|
1164
|
+
ctx,
|
|
1165
|
+
feat,
|
|
1166
|
+
renderArgs,
|
|
994
1167
|
defaultColor,
|
|
995
1168
|
colorForBase,
|
|
996
1169
|
contrastForBase,
|
|
997
1170
|
charWidth,
|
|
998
1171
|
charHeight,
|
|
1172
|
+
canvasWidth,
|
|
999
1173
|
})
|
|
1000
|
-
this.drawMismatches(
|
|
1174
|
+
this.drawMismatches({
|
|
1175
|
+
ctx,
|
|
1176
|
+
feat,
|
|
1177
|
+
renderArgs,
|
|
1001
1178
|
mismatchAlpha,
|
|
1002
|
-
drawSNPs
|
|
1003
|
-
drawIndels
|
|
1004
|
-
largeInsertionIndicatorScale
|
|
1179
|
+
drawSNPs,
|
|
1180
|
+
drawIndels,
|
|
1181
|
+
largeInsertionIndicatorScale,
|
|
1005
1182
|
minSubfeatureWidth,
|
|
1006
1183
|
charWidth,
|
|
1007
1184
|
charHeight,
|
|
1008
1185
|
colorForBase,
|
|
1009
1186
|
contrastForBase,
|
|
1187
|
+
canvasWidth,
|
|
1010
1188
|
})
|
|
1011
1189
|
if (showSoftClip) {
|
|
1012
|
-
this.drawSoftClipping(
|
|
1190
|
+
this.drawSoftClipping({
|
|
1191
|
+
ctx,
|
|
1192
|
+
feat,
|
|
1193
|
+
renderArgs,
|
|
1194
|
+
config,
|
|
1195
|
+
theme,
|
|
1196
|
+
canvasWidth,
|
|
1197
|
+
})
|
|
1013
1198
|
}
|
|
1014
|
-
}
|
|
1199
|
+
}
|
|
1015
1200
|
}
|
|
1016
1201
|
|
|
1017
1202
|
// we perform a full layout before render as a separate method because the
|
|
@@ -1041,7 +1226,7 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
1041
1226
|
|
|
1042
1227
|
const heightPx = readConfObject(config, 'height')
|
|
1043
1228
|
const displayMode = readConfObject(config, 'displayMode')
|
|
1044
|
-
|
|
1229
|
+
return iterMap(
|
|
1045
1230
|
featureMap.values(),
|
|
1046
1231
|
feature =>
|
|
1047
1232
|
this.layoutFeature({
|
|
@@ -1055,7 +1240,6 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
1055
1240
|
}),
|
|
1056
1241
|
featureMap.size,
|
|
1057
1242
|
)
|
|
1058
|
-
return layoutRecords
|
|
1059
1243
|
}
|
|
1060
1244
|
|
|
1061
1245
|
async fetchSequence(renderProps: RenderArgsDeserialized) {
|
|
@@ -1095,17 +1279,21 @@ export default class PileupRenderer extends BoxRendererType {
|
|
|
1095
1279
|
|
|
1096
1280
|
const width = (end - start) / bpPerPx
|
|
1097
1281
|
const height = Math.max(layout.getTotalHeight(), 1)
|
|
1098
|
-
|
|
1099
1282
|
const res = await renderToAbstractCanvas(
|
|
1100
1283
|
width,
|
|
1101
1284
|
height,
|
|
1102
1285
|
renderProps,
|
|
1103
1286
|
(ctx: CanvasRenderingContext2D) =>
|
|
1104
|
-
this.makeImageData(
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1287
|
+
this.makeImageData({
|
|
1288
|
+
ctx,
|
|
1289
|
+
layoutRecords,
|
|
1290
|
+
canvasWidth: width,
|
|
1291
|
+
renderArgs: {
|
|
1292
|
+
...renderProps,
|
|
1293
|
+
layout,
|
|
1294
|
+
features,
|
|
1295
|
+
regionSequence,
|
|
1296
|
+
},
|
|
1109
1297
|
}),
|
|
1110
1298
|
)
|
|
1111
1299
|
|