@jbrowse/plugin-alignments 1.7.8 → 1.7.11

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.
@@ -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,
@@ -132,8 +151,8 @@ export default class PileupRenderer extends BoxRendererType {
132
151
  // get width and height of chars the height is an approximation: width
133
152
  // letter M is approximately the height
134
153
  getCharWidthHeight(ctx: CanvasRenderingContext2D) {
135
- const charWidth = ctx.measureText('A').width
136
- const charHeight = ctx.measureText('M').width
154
+ const charWidth = measureText('A')
155
+ const charHeight = measureText('M')
137
156
  return { charWidth, charHeight }
138
157
  }
139
158
 
@@ -287,25 +306,32 @@ export default class PileupRenderer extends BoxRendererType {
287
306
  return strand === 1 ? 'color_fwd_strand' : 'color_rev_strand'
288
307
  }
289
308
 
290
- colorByPerBaseLettering(
291
- ctx: CanvasRenderingContext2D,
292
- feat: LayoutFeature,
293
- _config: AnyConfigurationModel,
294
- region: Region,
295
- bpPerPx: number,
296
- props: {
297
- colorForBase: Record<string, string>
298
- contrastForBase: Record<string, string>
299
- charWidth: number
300
- charHeight: number
301
- },
302
- ) {
303
- const { colorForBase, contrastForBase, charWidth, charHeight } = props
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
+ }) {
304
330
  const heightLim = charHeight - 2
305
331
  const { feature, topPx, heightPx } = feat
306
332
  const seq = feature.get('seq') as string
307
333
  const cigarOps = parseCigar(feature.get('CIGAR'))
308
- const widthPx = 1 / bpPerPx
334
+ const w = 1 / bpPerPx
309
335
  const start = feature.get('start')
310
336
  let soffset = 0 // sequence offset
311
337
  let roffset = 0 // reference offset
@@ -320,22 +346,25 @@ export default class PileupRenderer extends BoxRendererType {
320
346
  } else if (op === 'M' || op === 'X' || op === '=') {
321
347
  for (let m = 0; m < len; m++) {
322
348
  const letter = seq[soffset + m]
323
- ctx.fillStyle = colorForBase[letter]
324
- const [leftPx] = bpSpanPx(
325
- start + roffset + m,
326
- start + roffset + m + 1,
327
- region,
328
- bpPerPx,
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],
329
359
  )
330
- ctx.fillRect(leftPx, topPx, widthPx + 0.5, heightPx)
331
360
 
332
- if (widthPx >= charWidth && heightPx >= heightLim) {
361
+ if (w >= charWidth && heightPx >= heightLim) {
333
362
  // normal SNP coloring
334
363
  ctx.fillStyle = contrastForBase[letter]
335
364
 
336
365
  ctx.fillText(
337
366
  letter,
338
- leftPx + (widthPx - charWidth) / 2 + 1,
367
+ leftPx + (w - charWidth) / 2 + 1,
339
368
  topPx + heightPx,
340
369
  )
341
370
  }
@@ -345,13 +374,19 @@ export default class PileupRenderer extends BoxRendererType {
345
374
  }
346
375
  }
347
376
  }
348
- colorByPerBaseQuality(
349
- ctx: CanvasRenderingContext2D,
350
- feat: LayoutFeature,
351
- _config: AnyConfigurationModel,
352
- region: Region,
353
- bpPerPx: number,
354
- ) {
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
+ }) {
355
390
  const { feature, topPx, heightPx } = feat
356
391
  const qual: string = feature.get('qual') || ''
357
392
  const scores = qual.split(' ').map(val => +val)
@@ -371,14 +406,21 @@ export default class PileupRenderer extends BoxRendererType {
371
406
  } else if (op === 'M' || op === 'X' || op === '=') {
372
407
  for (let m = 0; m < len; m++) {
373
408
  const score = scores[soffset + m]
374
- ctx.fillStyle = `hsl(${score === 255 ? 150 : score * 1.5},55%,50%)`
375
409
  const [leftPx] = bpSpanPx(
376
410
  start + roffset + m,
377
411
  start + roffset + m + 1,
378
412
  region,
379
413
  bpPerPx,
380
414
  )
381
- ctx.fillRect(leftPx, topPx, width + 0.5, heightPx)
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
+ )
382
424
  }
383
425
  soffset += len
384
426
  roffset += len
@@ -395,16 +437,23 @@ export default class PileupRenderer extends BoxRendererType {
395
437
  // has very high likelihood basecalls at that point, we really only care
396
438
  // about low qual calls <20 approx
397
439
  //
398
- colorByModifications(
399
- ctx: CanvasRenderingContext2D,
400
- layoutFeature: LayoutFeature,
401
- _config: AnyConfigurationModel,
402
- region: Region,
403
- bpPerPx: number,
404
- props: RenderArgsDeserializedWithFeaturesAndLayout,
405
- ) {
406
- const { feature, topPx, heightPx } = layoutFeature
407
- const { modificationTagMap = {} } = props
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
408
457
 
409
458
  const mm = (getTagAlt(feature, 'MM', 'Mm') as string) || ''
410
459
 
@@ -440,14 +489,21 @@ export default class PileupRenderer extends BoxRendererType {
440
489
  // give it a little boost of 0.1 to not make them fully
441
490
  // invisible to avoid confusion
442
491
  const prob = probabilities[probIndex]
443
- ctx.fillStyle =
492
+
493
+ fillRect(
494
+ ctx,
495
+ leftPx,
496
+ topPx,
497
+ rightPx - leftPx + 0.5,
498
+ heightPx,
499
+ canvasWidth,
444
500
  prob && prob !== 1
445
501
  ? base
446
502
  .alpha(prob + 0.1)
447
503
  .hsl()
448
504
  .string()
449
- : col
450
- ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
505
+ : col,
506
+ )
451
507
  probIndex++
452
508
  }
453
509
  }
@@ -456,16 +512,23 @@ export default class PileupRenderer extends BoxRendererType {
456
512
  // Color by methylation is slightly modified version of color by
457
513
  // modifications that focuses on CpG sites, with non-methylated CpG colored
458
514
  // blue
459
- colorByMethylation(
460
- ctx: CanvasRenderingContext2D,
461
- layoutFeature: LayoutFeature,
462
- _config: AnyConfigurationModel,
463
- region: Region,
464
- bpPerPx: number,
465
- props: RenderArgsDeserializedWithFeaturesAndLayout,
466
- ) {
467
- const { regionSequence } = props
468
- const { feature, topPx, heightPx } = layoutFeature
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
469
532
 
470
533
  const mm: string = getTagAlt(feature, 'MM', 'Mm') || ''
471
534
 
@@ -505,8 +568,15 @@ export default class PileupRenderer extends BoxRendererType {
505
568
  if (l1 === 'c' && l2 === 'g') {
506
569
  const s = region.start + i
507
570
  const [leftPx, rightPx] = bpSpanPx(s, s + 2, region, bpPerPx)
508
- ctx.fillStyle = methBins[i] || methBins[i + 1] ? 'red' : 'blue'
509
- ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
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
+ )
510
580
  }
511
581
  }
512
582
  // if we are zoomed in, color the c inside the cpg
@@ -515,12 +585,26 @@ export default class PileupRenderer extends BoxRendererType {
515
585
  if (l1 === 'c' && l2 === 'g') {
516
586
  const s = region.start + i
517
587
  const [leftPx, rightPx] = bpSpanPx(s, s + 1, region, bpPerPx)
518
- ctx.fillStyle = methBins[i] ? 'red' : 'blue'
519
- ctx.fillRect(leftPx, topPx, rightPx - leftPx + 0.5, heightPx)
588
+ fillRect(
589
+ ctx,
590
+ leftPx,
591
+ topPx,
592
+ rightPx - leftPx + 0.5,
593
+ heightPx,
594
+ canvasWidth,
595
+ methBins[i] ? 'red' : 'blue',
596
+ )
520
597
 
521
598
  const [leftPx2, rightPx2] = bpSpanPx(s + 1, s + 2, region, bpPerPx)
522
- ctx.fillStyle = methBins[i + 1] ? 'red' : 'blue'
523
- ctx.fillRect(leftPx2, topPx, rightPx2 - leftPx2 + 0.5, heightPx)
599
+ fillRect(
600
+ ctx,
601
+ leftPx2,
602
+ topPx,
603
+ rightPx2 - leftPx2 + 0.5,
604
+ heightPx,
605
+ canvasWidth,
606
+ methBins[i + 1] ? 'red' : 'blue',
607
+ )
524
608
  }
525
609
  }
526
610
  }
@@ -568,29 +652,29 @@ export default class PileupRenderer extends BoxRendererType {
568
652
  }
569
653
  }
570
654
 
571
- drawAlignmentRect(
572
- ctx: CanvasRenderingContext2D,
573
- feat: LayoutFeature,
574
- props: RenderArgsDeserializedWithFeaturesAndLayout & {
575
- colorForBase: Record<string, string>
576
- contrastForBase: Record<string, string>
577
- charWidth: number
578
- charHeight: number
579
- defaultColor: boolean
580
- },
581
- ) {
582
- const {
583
- defaultColor,
584
- config,
585
- bpPerPx,
586
- regions,
587
- colorBy,
588
- colorTagMap = {},
589
- colorForBase,
590
- contrastForBase,
591
- charWidth,
592
- charHeight,
593
- } = props
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
+
594
678
  const { tag = '', type: colorType = '' } = colorBy || {}
595
679
  const { feature } = feat
596
680
  const region = regions[0]
@@ -677,62 +761,89 @@ export default class PileupRenderer extends BoxRendererType {
677
761
  break
678
762
  }
679
763
 
680
- this.drawRect(ctx, feat, props)
764
+ this.drawRect(ctx, feat, renderArgs)
681
765
 
682
766
  // second pass for color types that render per-base things that go over the
683
767
  // existing drawing
684
768
  switch (colorType) {
685
769
  case 'perBaseQuality':
686
- this.colorByPerBaseQuality(ctx, feat, config, region, bpPerPx)
770
+ this.colorByPerBaseQuality({
771
+ ctx,
772
+ feat,
773
+ region,
774
+ bpPerPx,
775
+ canvasWidth,
776
+ })
687
777
  break
688
778
 
689
779
  case 'perBaseLettering':
690
- this.colorByPerBaseLettering(ctx, feat, config, region, bpPerPx, {
780
+ this.colorByPerBaseLettering({
781
+ ctx,
782
+ feat,
783
+ region,
784
+ bpPerPx,
691
785
  colorForBase,
692
786
  contrastForBase,
693
787
  charWidth,
694
788
  charHeight,
789
+ canvasWidth,
695
790
  })
696
791
  break
697
792
 
698
793
  case 'modifications':
699
- this.colorByModifications(ctx, feat, config, region, bpPerPx, props)
794
+ this.colorByModifications({
795
+ ctx,
796
+ feat,
797
+ region,
798
+ bpPerPx,
799
+ renderArgs,
800
+ canvasWidth,
801
+ })
700
802
  break
701
803
 
702
804
  case 'methylation':
703
- this.colorByMethylation(ctx, feat, config, region, bpPerPx, props)
805
+ this.colorByMethylation({
806
+ ctx,
807
+ feat,
808
+ region,
809
+ bpPerPx,
810
+ renderArgs,
811
+ canvasWidth,
812
+ })
704
813
  break
705
814
  }
706
815
  }
707
816
 
708
- drawMismatches(
709
- ctx: CanvasRenderingContext2D,
710
- feat: LayoutFeature,
711
- props: RenderArgsDeserializedWithFeaturesAndLayout,
712
- opts: {
713
- colorForBase: { [key: string]: string }
714
- contrastForBase: { [key: string]: string }
715
- mismatchAlpha?: boolean
716
- drawSNPs?: boolean
717
- drawIndels?: boolean
718
- minSubfeatureWidth: number
719
- largeInsertionIndicatorScale: number
720
- charWidth: number
721
- charHeight: number
722
- },
723
- ) {
724
- const {
725
- minSubfeatureWidth,
726
- largeInsertionIndicatorScale,
727
- mismatchAlpha,
728
- drawSNPs = true,
729
- drawIndels = true,
730
- charWidth,
731
- charHeight,
732
- colorForBase,
733
- contrastForBase,
734
- } = opts
735
- const { bpPerPx, regions } = props
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
736
847
  const { heightPx, topPx, feature } = feat
737
848
  const [region] = regions
738
849
  const start = feature.get('start')
@@ -760,16 +871,22 @@ export default class PileupRenderer extends BoxRendererType {
760
871
  if (mismatch.type === 'mismatch' && drawSNPs) {
761
872
  const baseColor = colorForBase[mismatch.base] || '#888'
762
873
 
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
771
-
772
- ctx.fillRect(leftPx, topPx, widthPx, heightPx)
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
+ )
773
890
 
774
891
  if (widthPx >= charWidth && heightPx >= heightLim) {
775
892
  // normal SNP coloring
@@ -789,9 +906,15 @@ export default class PileupRenderer extends BoxRendererType {
789
906
  )
790
907
  }
791
908
  } else if (mismatch.type === 'deletion' && drawIndels) {
792
- const baseColor = colorForBase.deletion
793
- ctx.fillStyle = baseColor
794
- ctx.fillRect(leftPx, topPx, widthPx, heightPx)
909
+ fillRect(
910
+ ctx,
911
+ leftPx,
912
+ topPx,
913
+ widthPx,
914
+ heightPx,
915
+ canvasWidth,
916
+ colorForBase.deletion,
917
+ )
795
918
  const txt = `${mismatch.length}`
796
919
  const rwidth = measureText(txt, 10)
797
920
  if (widthPx >= rwidth && heightPx >= heightLim) {
@@ -811,20 +934,34 @@ export default class PileupRenderer extends BoxRendererType {
811
934
  Math.min(1.2, 1 / bpPerPx),
812
935
  )
813
936
  if (len < 10) {
814
- ctx.fillRect(pos, topPx, insW, heightPx)
937
+ fillRect(ctx, pos, topPx, insW, heightPx, canvasWidth, 'purple')
815
938
  if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
816
- ctx.fillRect(pos - insW, topPx, insW * 3, 1)
817
- ctx.fillRect(pos - insW, topPx + heightPx - 1, insW * 3, 1)
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
+ )
818
948
  ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
819
949
  }
820
950
  }
821
951
  } else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
822
- ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
823
952
  const pos = leftPx + extraHorizontallyFlippedOffset
824
- ctx.fillRect(pos, topPx, w, heightPx)
953
+ fillRect(
954
+ ctx,
955
+ pos,
956
+ topPx,
957
+ w,
958
+ heightPx,
959
+ canvasWidth,
960
+ mismatch.type === 'hardclip' ? 'red' : 'blue',
961
+ )
825
962
  if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
826
- ctx.fillRect(pos - w, topPx, w * 3, 1)
827
- ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
963
+ fillRect(ctx, pos - w, topPx, w * 3, 1, canvasWidth)
964
+ fillRect(ctx, pos - w, topPx + heightPx - 1, w * 3, 1, canvasWidth)
828
965
  ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
829
966
  }
830
967
  } else if (mismatch.type === 'skip') {
@@ -835,12 +972,14 @@ export default class PileupRenderer extends BoxRendererType {
835
972
  // make small exons more visible when zoomed far out
836
973
  const adjustPx = widthPx - (bpPerPx > 10 ? 1.5 : 0)
837
974
  ctx.clearRect(leftPx, topPx, adjustPx, heightPx)
838
- ctx.fillStyle = '#333'
839
- ctx.fillRect(
975
+ fillRect(
976
+ ctx,
840
977
  Math.max(0, leftPx),
841
978
  topPx + heightPx / 2 - 1,
842
979
  adjustPx + (leftPx < 0 ? leftPx : 0),
843
980
  2,
981
+ canvasWidth,
982
+ '#333',
844
983
  )
845
984
  }
846
985
  }
@@ -857,39 +996,55 @@ export default class PileupRenderer extends BoxRendererType {
857
996
  const txt = `${len}`
858
997
  if (mismatch.type === 'insertion' && len >= 10) {
859
998
  if (bpPerPx > largeInsertionIndicatorScale) {
860
- ctx.fillStyle = 'purple'
861
- ctx.fillRect(leftPx - 1, topPx, 2, heightPx)
999
+ fillRect(ctx, leftPx - 1, topPx, 2, heightPx, canvasWidth, 'purple')
862
1000
  } else if (heightPx > charHeight) {
863
1001
  const rwidth = measureText(txt)
864
1002
  const padding = 5
865
- ctx.fillStyle = 'purple'
866
- ctx.fillRect(
1003
+ fillRect(
1004
+ ctx,
867
1005
  leftPx - rwidth / 2 - padding,
868
1006
  topPx,
869
1007
  rwidth + 2 * padding,
870
1008
  heightPx,
1009
+ canvasWidth,
1010
+ 'purple',
871
1011
  )
872
1012
  ctx.fillStyle = 'white'
873
1013
  ctx.fillText(txt, leftPx - rwidth / 2, topPx + heightPx)
874
1014
  } else {
875
1015
  const padding = 2
876
- ctx.fillStyle = 'purple'
877
- ctx.fillRect(leftPx - padding, topPx, 2 * padding, heightPx)
1016
+ fillRect(
1017
+ ctx,
1018
+ leftPx - padding,
1019
+ topPx,
1020
+ 2 * padding,
1021
+ heightPx,
1022
+ canvasWidth,
1023
+ 'purple',
1024
+ )
878
1025
  }
879
1026
  }
880
1027
  }
881
1028
  }
882
1029
  }
883
1030
 
884
- drawSoftClipping(
885
- ctx: CanvasRenderingContext2D,
886
- feat: LayoutFeature,
887
- props: RenderArgsDeserializedWithFeaturesAndLayout,
888
- config: AnyConfigurationModel,
889
- theme: Theme,
890
- ) {
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
+ }) {
891
1046
  const { feature, topPx, heightPx } = feat
892
- const { regions, bpPerPx } = props
1047
+ const { regions, bpPerPx } = renderArgs
893
1048
  const [region] = regions
894
1049
  const minFeatWidth = readConfObject(config, 'minSubfeatureWidth')
895
1050
  const mismatches: Mismatch[] = feature.get('mismatches')
@@ -937,7 +1092,14 @@ export default class PileupRenderer extends BoxRendererType {
937
1092
  // show in soft clipping
938
1093
  const baseColor = colorForBase[base] || '#000000'
939
1094
  ctx.fillStyle = baseColor
940
- ctx.fillRect(softClipLeftPx, topPx, softClipWidthPx, heightPx)
1095
+ fillRect(
1096
+ ctx,
1097
+ softClipLeftPx,
1098
+ topPx,
1099
+ softClipWidthPx,
1100
+ heightPx,
1101
+ canvasWidth,
1102
+ )
941
1103
 
942
1104
  if (softClipWidthPx >= charWidth && heightPx >= charHeight - 5) {
943
1105
  ctx.fillStyle = theme.palette.getContrastText(baseColor)
@@ -952,12 +1114,24 @@ export default class PileupRenderer extends BoxRendererType {
952
1114
  }
953
1115
  }
954
1116
 
955
- makeImageData(
956
- ctx: CanvasRenderingContext2D,
957
- layoutRecords: (LayoutFeature | null)[],
958
- props: RenderArgsDeserializedWithFeaturesAndLayout,
959
- ) {
960
- const { layout, config, showSoftClip, colorBy, theme: configTheme } = props
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
961
1135
  const mismatchAlpha = readConfObject(config, 'mismatchAlpha')
962
1136
  const minSubfeatureWidth = readConfObject(config, 'minSubfeatureWidth')
963
1137
  const largeInsertionIndicatorScale = readConfObject(
@@ -986,15 +1160,21 @@ export default class PileupRenderer extends BoxRendererType {
986
1160
  continue
987
1161
  }
988
1162
 
989
- this.drawAlignmentRect(ctx, feat, {
990
- ...props,
1163
+ this.drawAlignmentRect({
1164
+ ctx,
1165
+ feat,
1166
+ renderArgs,
991
1167
  defaultColor,
992
1168
  colorForBase,
993
1169
  contrastForBase,
994
1170
  charWidth,
995
1171
  charHeight,
1172
+ canvasWidth,
996
1173
  })
997
- this.drawMismatches(ctx, feat, props, {
1174
+ this.drawMismatches({
1175
+ ctx,
1176
+ feat,
1177
+ renderArgs,
998
1178
  mismatchAlpha,
999
1179
  drawSNPs,
1000
1180
  drawIndels,
@@ -1004,15 +1184,17 @@ export default class PileupRenderer extends BoxRendererType {
1004
1184
  charHeight,
1005
1185
  colorForBase,
1006
1186
  contrastForBase,
1187
+ canvasWidth,
1007
1188
  })
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
- }
1015
- this.drawSoftClipping(ctx, feat, props, config, theme)
1189
+ if (showSoftClip) {
1190
+ this.drawSoftClipping({
1191
+ ctx,
1192
+ feat,
1193
+ renderArgs,
1194
+ config,
1195
+ theme,
1196
+ canvasWidth,
1197
+ })
1016
1198
  }
1017
1199
  }
1018
1200
  }
@@ -1097,17 +1279,21 @@ export default class PileupRenderer extends BoxRendererType {
1097
1279
 
1098
1280
  const width = (end - start) / bpPerPx
1099
1281
  const height = Math.max(layout.getTotalHeight(), 1)
1100
-
1101
1282
  const res = await renderToAbstractCanvas(
1102
1283
  width,
1103
1284
  height,
1104
1285
  renderProps,
1105
1286
  (ctx: CanvasRenderingContext2D) =>
1106
- this.makeImageData(ctx, layoutRecords, {
1107
- ...renderProps,
1108
- layout,
1109
- features,
1110
- regionSequence,
1287
+ this.makeImageData({
1288
+ ctx,
1289
+ layoutRecords,
1290
+ canvasWidth: width,
1291
+ renderArgs: {
1292
+ ...renderProps,
1293
+ layout,
1294
+ features,
1295
+ regionSequence,
1296
+ },
1111
1297
  }),
1112
1298
  )
1113
1299