@jbrowse/plugin-alignments 1.6.4 → 1.6.7

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.
Files changed (35) hide show
  1. package/dist/AlignmentsFeatureDetail/index.d.ts +1 -1
  2. package/dist/BamAdapter/BamSlightlyLazyFeature.d.ts +3 -2
  3. package/dist/BamAdapter/configSchema.d.ts +1 -1
  4. package/dist/CramAdapter/configSchema.d.ts +1 -1
  5. package/dist/HtsgetBamAdapter/configSchema.d.ts +1 -1
  6. package/dist/LinearAlignmentsDisplay/models/configSchema.d.ts +1 -1
  7. package/dist/LinearAlignmentsDisplay/models/model.d.ts +1 -1
  8. package/dist/LinearPileupDisplay/configSchema.d.ts +1 -1
  9. package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
  10. package/dist/LinearSNPCoverageDisplay/models/configSchema.d.ts +1 -1
  11. package/dist/PileupRenderer/configSchema.d.ts +1 -1
  12. package/dist/SNPCoverageAdapter/configSchema.d.ts +1 -1
  13. package/dist/SNPCoverageRenderer/SNPCoverageRenderer.d.ts +1 -1
  14. package/dist/SNPCoverageRenderer/configSchema.d.ts +1 -1
  15. package/dist/SNPCoverageRenderer/index.d.ts +1 -1
  16. package/dist/plugin-alignments.cjs.development.js +291 -223
  17. package/dist/plugin-alignments.cjs.development.js.map +1 -1
  18. package/dist/plugin-alignments.cjs.production.min.js +1 -1
  19. package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
  20. package/dist/plugin-alignments.esm.js +291 -223
  21. package/dist/plugin-alignments.esm.js.map +1 -1
  22. package/package.json +6 -6
  23. package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +23 -14
  24. package/src/BamAdapter/BamAdapter.ts +3 -4
  25. package/src/BamAdapter/BamSlightlyLazyFeature.ts +8 -4
  26. package/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx +38 -30
  27. package/src/LinearAlignmentsDisplay/models/model.tsx +10 -9
  28. package/src/LinearPileupDisplay/model.ts +6 -6
  29. package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +5 -3
  30. package/src/LinearSNPCoverageDisplay/models/configSchema.ts +4 -5
  31. package/src/PileupRenderer/PileupRenderer.tsx +39 -27
  32. package/src/PileupRenderer/components/PileupRendering.tsx +5 -3
  33. package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +188 -169
  34. package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +86 -56
  35. package/src/SNPCoverageRenderer/configSchema.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-alignments",
3
- "version": "1.6.4",
3
+ "version": "1.6.7",
4
4
  "description": "JBrowse 2 alignments adapters, tracks, etc.",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -35,16 +35,16 @@
35
35
  "useSrc": "node ../../scripts/useSrc.js"
36
36
  },
37
37
  "dependencies": {
38
- "@gmod/bam": "^1.1.11",
39
- "@gmod/cram": "^1.5.9",
38
+ "@gmod/bam": "^1.1.14",
39
+ "@gmod/cram": "^1.6.1",
40
40
  "@material-ui/icons": "^4.9.1",
41
- "abortable-promise-cache": "^1.1.3",
41
+ "abortable-promise-cache": "^1.5.0",
42
42
  "color": "^3.1.2",
43
43
  "copy-to-clipboard": "^3.3.1",
44
44
  "fast-deep-equal": "^3.1.3",
45
45
  "generic-filehandle": "^2.2.2",
46
46
  "json-stable-stringify": "^1.0.1",
47
- "react-d3-axis": "^0.1.2"
47
+ "react-d3-axis-mod": "^0.1.3"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@jbrowse/core": "^1.0.0",
@@ -61,5 +61,5 @@
61
61
  "publishConfig": {
62
62
  "access": "public"
63
63
  },
64
- "gitHead": "7f157fbb2302d8825f99d01bcc0fd0616116c5d2"
64
+ "gitHead": "02012ec299c36647f755316571775d36b0fee5ec"
65
65
  }
@@ -136,13 +136,17 @@ function SupplementaryAlignments(props: { tag: string; model: any }) {
136
136
  <Link
137
137
  onClick={() => {
138
138
  const { view } = model
139
- if (view) {
140
- view.navToLocString(locString)
141
- } else {
142
- session.notify(
143
- 'No view associated with this feature detail panel anymore',
144
- 'warning',
145
- )
139
+ try {
140
+ if (view) {
141
+ view.navToLocString(locString)
142
+ } else {
143
+ throw new Error(
144
+ 'No view associated with this view anymore',
145
+ )
146
+ }
147
+ } catch (e) {
148
+ console.error(e)
149
+ session.notify(`${e}`)
146
150
  }
147
151
  }}
148
152
  href="#"
@@ -164,13 +168,18 @@ function PairLink({ locString, model }: { locString: string; model: any }) {
164
168
  <Link
165
169
  onClick={() => {
166
170
  const { view } = model
167
- if (view) {
168
- view.navToLocString(locString)
169
- } else {
170
- session.notify(
171
- 'No view associated with this feature detail panel anymore',
172
- 'warning',
173
- )
171
+ try {
172
+ if (view) {
173
+ view.navToLocString(locString)
174
+ } else {
175
+ session.notify(
176
+ 'No view associated with this feature detail panel anymore',
177
+ 'warning',
178
+ )
179
+ }
180
+ } catch (e) {
181
+ console.error(e)
182
+ session.notify(`${e}`)
174
183
  }
175
184
  }}
176
185
  href="#"
@@ -197,11 +197,10 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
197
197
 
198
198
  async estimateRegionsStats(regions: Region[], opts?: BaseOptions) {
199
199
  const { bam } = await this.configure()
200
- // @ts-ignore
201
- const index = bam.index
202
200
  // this is a method to avoid calling on htsget adapters
203
- if (index.filehandle !== '?') {
204
- const bytes = await bytesForRegions(regions, index)
201
+ // @ts-ignore
202
+ if (bam.index.filehandle !== '?') {
203
+ const bytes = await bytesForRegions(regions, bam)
205
204
  const fetchSizeLimit = readConfObject(this.config, 'fetchSizeLimit')
206
205
  return { bytes, fetchSizeLimit }
207
206
  } else {
@@ -15,6 +15,7 @@ import {
15
15
  import BamAdapter from './BamAdapter'
16
16
 
17
17
  export default class BamSlightlyLazyFeature implements Feature {
18
+ private cachedMD = ''
18
19
  constructor(
19
20
  private record: BamRecord,
20
21
  private adapter: BamAdapter,
@@ -70,10 +71,13 @@ export default class BamSlightlyLazyFeature implements Feature {
70
71
  }
71
72
 
72
73
  _get_MD() {
73
- const md = this.record.get('MD') as string | undefined
74
- const seq = this.get('seq') as string
75
- if (!md && seq && this.ref) {
76
- return generateMD(this.ref, this.record.getReadBases(), this.get('CIGAR'))
74
+ const md = this.record.get('MD') || this.cachedMD
75
+ if (!md) {
76
+ const seq = this.get('seq')
77
+ if (seq && this.ref) {
78
+ this.cachedMD = generateMD(this.ref, this.get('seq'), this.get('CIGAR'))
79
+ return this.cachedMD
80
+ }
77
81
  }
78
82
  return md
79
83
  }
@@ -2,47 +2,55 @@ import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { getConf } from '@jbrowse/core/configuration'
4
4
  import { ResizeHandle } from '@jbrowse/core/ui'
5
+ import { makeStyles } from '@material-ui/core'
5
6
  import { AlignmentsDisplayModel } from '../models/model'
6
7
 
8
+ const useStyles = makeStyles(() => ({
9
+ resizeHandle: {
10
+ height: 2,
11
+ position: 'absolute',
12
+ zIndex: 2,
13
+ },
14
+ }))
15
+
7
16
  function AlignmentsDisplay({ model }: { model: AlignmentsDisplayModel }) {
8
17
  const { PileupDisplay, SNPCoverageDisplay, showPileup, showCoverage } = model
18
+ const classes = useStyles()
19
+ const top = SNPCoverageDisplay.height
9
20
  return (
10
21
  <div
11
22
  data-testid={`display-${getConf(model, 'displayId')}`}
12
23
  style={{ position: 'relative' }}
13
24
  >
14
- <div data-testid="Blockset-snpcoverage">
15
- {showCoverage ? (
16
- <SNPCoverageDisplay.RenderingComponent model={SNPCoverageDisplay} />
17
- ) : null}
18
- </div>
19
- <ResizeHandle
20
- onDrag={delta => {
21
- if (SNPCoverageDisplay) {
22
- SNPCoverageDisplay.setHeight(SNPCoverageDisplay.height + delta)
23
- return delta
24
- }
25
- return 0
26
- }}
27
- style={{
28
- position: 'absolute',
29
- top: showCoverage ? SNPCoverageDisplay.height + 2 : 0,
30
- height: 3,
31
- }}
32
- />
25
+ {showCoverage ? (
26
+ <>
27
+ <div data-testid="Blockset-snpcoverage">
28
+ <SNPCoverageDisplay.RenderingComponent model={SNPCoverageDisplay} />
29
+ </div>
30
+ <ResizeHandle
31
+ onDrag={delta => {
32
+ SNPCoverageDisplay.setHeight(SNPCoverageDisplay.height + delta)
33
+ return delta
34
+ }}
35
+ className={classes.resizeHandle}
36
+ style={{
37
+ top,
38
+ }}
39
+ />
40
+ </>
41
+ ) : null}
33
42
 
34
- <div
35
- data-testid="Blockset-pileup"
36
- style={{
37
- position: 'absolute',
38
- top: showCoverage ? SNPCoverageDisplay.height + 5 : 0,
39
- height: 3,
40
- }}
41
- >
42
- {showPileup ? (
43
+ {showPileup ? (
44
+ <div
45
+ data-testid="Blockset-pileup"
46
+ style={{
47
+ position: 'absolute',
48
+ top: showCoverage ? SNPCoverageDisplay.height : 0,
49
+ }}
50
+ >
43
51
  <PileupDisplay.RenderingComponent model={PileupDisplay} />
44
- ) : null}
45
- </div>
52
+ </div>
53
+ ) : null}
46
54
  </div>
47
55
  )
48
56
  }
@@ -1,13 +1,16 @@
1
1
  import React from 'react'
2
- import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
3
- import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
2
+ import {
3
+ ConfigurationReference,
4
+ AnyConfigurationModel,
5
+ getConf,
6
+ } from '@jbrowse/core/configuration'
4
7
  import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes/models'
5
8
  import PluginManager from '@jbrowse/core/PluginManager'
6
9
  import { MenuItem } from '@jbrowse/core/ui'
7
- import deepEqual from 'fast-deep-equal'
8
10
  import { autorun, when } from 'mobx'
9
11
  import { addDisposer, getSnapshot, Instance, types } from 'mobx-state-tree'
10
12
  import { getContainingTrack } from '@jbrowse/core/util'
13
+ import deepEqual from 'fast-deep-equal'
11
14
  import { AlignmentsConfigModel } from './configSchema'
12
15
 
13
16
  const minDisplayHeight = 20
@@ -222,12 +225,10 @@ const stateModelFactory = (
222
225
  <>
223
226
  <g>{await self.SNPCoverageDisplay.renderSvg(opts)}</g>
224
227
  <g transform={`translate(0 ${self.SNPCoverageDisplay.height})`}>
225
- {
226
- await self.PileupDisplay.renderSvg({
227
- ...opts,
228
- overrideHeight: pileupHeight,
229
- })
230
- }
228
+ {await self.PileupDisplay.renderSvg({
229
+ ...opts,
230
+ overrideHeight: pileupHeight,
231
+ })}
231
232
  </g>
232
233
  </>
233
234
  )
@@ -460,7 +460,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
460
460
  {
461
461
  label: 'Sort by tag...',
462
462
  onClick: () => {
463
- getSession(self).queueDialog((doneCallback: Function) => [
463
+ getSession(self).queueDialog(doneCallback => [
464
464
  SortByTagDlg,
465
465
  { model: self, handleClose: doneCallback },
466
466
  ])
@@ -509,7 +509,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
509
509
  {
510
510
  label: 'Modifications or methylation',
511
511
  onClick: () => {
512
- getSession(self).queueDialog((doneCallback: Function) => [
512
+ getSession(self).queueDialog(doneCallback => [
513
513
  ModificationsDlg,
514
514
  { model: self, handleClose: doneCallback },
515
515
  ])
@@ -530,7 +530,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
530
530
  {
531
531
  label: 'Color by tag...',
532
532
  onClick: () => {
533
- getSession(self).queueDialog((doneCallback: Function) => [
533
+ getSession(self).queueDialog(doneCallback => [
534
534
  ColorByTagDlg,
535
535
  { model: self, handleClose: doneCallback },
536
536
  ])
@@ -542,7 +542,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
542
542
  label: 'Filter by',
543
543
  icon: FilterListIcon,
544
544
  onClick: () => {
545
- getSession(self).queueDialog((doneCallback: Function) => [
545
+ getSession(self).queueDialog(doneCallback => [
546
546
  FilterByTagDlg,
547
547
  { model: self, handleClose: doneCallback },
548
548
  ])
@@ -551,7 +551,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
551
551
  {
552
552
  label: 'Set feature height',
553
553
  onClick: () => {
554
- getSession(self).queueDialog((doneCallback: Function) => [
554
+ getSession(self).queueDialog(doneCallback => [
555
555
  SetFeatureHeightDlg,
556
556
  { model: self, handleClose: doneCallback },
557
557
  ])
@@ -560,7 +560,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
560
560
  {
561
561
  label: 'Set max height',
562
562
  onClick: () => {
563
- getSession(self).queueDialog((doneCallback: Function) => [
563
+ getSession(self).queueDialog(doneCallback => [
564
564
  SetMaxHeightDlg,
565
565
  { model: self, handleClose: doneCallback },
566
566
  ])
@@ -27,11 +27,11 @@ const TooltipContents = React.forwardRef(
27
27
  const start = feature.get('start')
28
28
  const end = feature.get('end')
29
29
  const name = feature.get('refName')
30
+ const info = feature.get('snpinfo') as SNPInfo
30
31
  const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
31
32
  .filter(f => !!f)
32
33
  .join(':')
33
34
 
34
- const info = feature.get('snpinfo') as SNPInfo
35
35
  const total = info?.total
36
36
 
37
37
  return (
@@ -64,7 +64,9 @@ const TooltipContents = React.forwardRef(
64
64
  <td>
65
65
  {base === 'total' || base === 'skip'
66
66
  ? '---'
67
- : `${Math.floor((score.total / total) * 100)}%`}
67
+ : `${Math.floor(
68
+ (score.total / (total || score.total || 1)) * 100,
69
+ )}%`}
68
70
  </td>
69
71
  <td>
70
72
  {strands['-1'] ? `${strands['-1']}(-)` : ''}
@@ -90,7 +92,7 @@ const SNPCoverageTooltip = observer(
90
92
  height: number
91
93
  offsetMouseCoord: Coord
92
94
  clientMouseCoord: Coord
93
- clientRect?: ClientRect
95
+ clientRect?: DOMRect
94
96
  }) => {
95
97
  const { model } = props
96
98
  const { featureUnderMouse: feat } = model
@@ -40,11 +40,10 @@ export default function SNPCoverageConfigFactory(pluginManager: PluginManager) {
40
40
  defaultValue: false,
41
41
  },
42
42
 
43
- headroom: {
44
- type: 'number',
45
- description:
46
- 'round the upper value of the domain scale to the nearest N',
47
- defaultValue: 0,
43
+ multiTicks: {
44
+ type: 'boolean',
45
+ description: 'Display multiple values for the ticks',
46
+ defaultValue: false,
48
47
  },
49
48
 
50
49
  renderers: ConfigurationSchema('RenderersConfiguration', {
@@ -345,7 +345,8 @@ export default class PileupRenderer extends BoxRendererType {
345
345
 
346
346
  // probIndex applies across multiple modifications e.g.
347
347
  let probIndex = 0
348
- modifications.forEach(({ type, positions }) => {
348
+ for (let i = 0; i < modifications.length; i++) {
349
+ const { type, positions } = modifications[i]
349
350
  const col = modificationTagMap[type] || 'black'
350
351
  const base = Color(col)
351
352
  for (const readPos of getNextRefPos(cigarOps, positions)) {
@@ -367,7 +368,7 @@ export default class PileupRenderer extends BoxRendererType {
367
368
  }
368
369
  probIndex++
369
370
  }
370
- })
371
+ }
371
372
  }
372
373
 
373
374
  // Color by methylation is slightly modified version of color by
@@ -399,7 +400,9 @@ export default class PileupRenderer extends BoxRendererType {
399
400
  const { start: rstart, end: rend } = region
400
401
 
401
402
  const methBins = new Array(rend - rstart).fill(0)
402
- getModificationPositions(mm, seq, strand).forEach(({ type, positions }) => {
403
+ const modifications = getModificationPositions(mm, seq, strand)
404
+ for (let i = 0; i < modifications.length; i++) {
405
+ const { type, positions } = modifications[i]
403
406
  if (type === 'm' && positions) {
404
407
  for (const pos of getNextRefPos(cigarOps, positions)) {
405
408
  const epos = pos + fstart - rstart
@@ -408,7 +411,7 @@ export default class PileupRenderer extends BoxRendererType {
408
411
  }
409
412
  }
410
413
  }
411
- })
414
+ }
412
415
 
413
416
  for (let j = fstart; j < fend; j++) {
414
417
  const i = j - rstart
@@ -650,6 +653,12 @@ export default class PileupRenderer extends BoxRendererType {
650
653
  return color
651
654
  }
652
655
 
656
+ // extraHorizontallyFlippedOffset is used to draw interbase items, which
657
+ // are located to the left when forward and right when reversed
658
+ const extraHorizontallyFlippedOffset = region.reversed
659
+ ? 1 / bpPerPx + 1
660
+ : -1
661
+
653
662
  // two pass rendering: first pass, draw all the mismatches except wide
654
663
  // insertion markers
655
664
  for (let i = 0; i < mismatches.length; i += 1) {
@@ -694,42 +703,46 @@ export default class PileupRenderer extends BoxRendererType {
694
703
  }
695
704
  } else if (mismatch.type === 'insertion' && drawIndels) {
696
705
  ctx.fillStyle = 'purple'
697
- const pos = leftPx - 1
706
+ const pos = leftPx + extraHorizontallyFlippedOffset
698
707
  const len = +mismatch.base || mismatch.length
708
+ const insW = Math.max(minWidth, Math.min(1.2, 1 / bpPerPx))
699
709
  if (len < 10) {
700
- ctx.fillRect(pos, topPx, w, heightPx)
710
+ ctx.fillRect(pos, topPx, insW, heightPx)
701
711
  if (1 / bpPerPx >= charWidth) {
702
- ctx.fillRect(pos - w, topPx, w * 3, 1)
703
- ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
712
+ ctx.fillRect(pos - insW, topPx, insW * 3, 1)
713
+ ctx.fillRect(pos - insW, topPx + heightPx - 1, insW * 3, 1)
704
714
  }
705
715
  if (1 / bpPerPx >= charWidth && heightPx >= heightLim) {
706
- ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
716
+ ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
707
717
  }
708
718
  }
709
719
  } else if (mismatch.type === 'hardclip' || mismatch.type === 'softclip') {
710
720
  ctx.fillStyle = mismatch.type === 'hardclip' ? 'red' : 'blue'
711
- const pos = leftPx - 1
712
- ctx.fillRect(pos, topPx + 1, w, heightPx - 2)
713
- ctx.fillRect(pos - w, topPx, w * 3, 1)
714
- ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
721
+ const pos = leftPx + extraHorizontallyFlippedOffset
722
+ ctx.fillRect(pos, topPx, w, heightPx)
723
+ if (1 / bpPerPx >= charWidth) {
724
+ ctx.fillRect(pos - w, topPx, w * 3, 1)
725
+ ctx.fillRect(pos - w, topPx + heightPx - 1, w * 3, 1)
726
+ }
715
727
  if (widthPx >= charWidth && heightPx >= heightLim) {
716
- ctx.fillText(`(${mismatch.base})`, leftPx + 2, topPx + heightPx)
728
+ ctx.fillText(`(${mismatch.base})`, pos + 3, topPx + heightPx)
717
729
  }
718
730
  } else if (mismatch.type === 'skip') {
719
- // fix to avoid bad rendering
720
- // note that this was also related to chrome bug https://bugs.chromium.org/p/chro>
721
- // ref #1236
731
+ // fix to avoid bad rendering note that this was also related to chrome
732
+ // bug https://bugs.chromium.org/p/chromium/issues/detail?id=1131528
733
+ // also affected firefox ref #1236 #2750
722
734
  if (leftPx + widthPx > 0) {
723
735
  // make small exons more visible when zoomed far out
724
- ctx.clearRect(
725
- leftPx,
726
- topPx,
727
- widthPx - (bpPerPx > 10 ? 1.5 : 0),
728
- heightPx,
736
+ const adjustPx = widthPx - (bpPerPx > 10 ? 1.5 : 0)
737
+ ctx.clearRect(leftPx, topPx, adjustPx, heightPx)
738
+ ctx.fillStyle = '#333'
739
+ ctx.fillRect(
740
+ Math.max(0, leftPx),
741
+ topPx + heightPx / 2 - 1,
742
+ adjustPx + (leftPx < 0 ? leftPx : 0),
743
+ 2,
729
744
  )
730
745
  }
731
- ctx.fillStyle = '#333'
732
- ctx.fillRect(leftPx, topPx + heightPx / 2, widthPx, 2)
733
746
  }
734
747
  }
735
748
 
@@ -796,10 +809,9 @@ export default class PileupRenderer extends BoxRendererType {
796
809
  .filter(mismatch => mismatch.type === 'softclip')
797
810
  .forEach(mismatch => {
798
811
  const softClipLength = mismatch.cliplen || 0
812
+ const s = feature.get('start')
799
813
  const softClipStart =
800
- mismatch.start === 0
801
- ? feature.get('start') - softClipLength
802
- : feature.get('start') + mismatch.start
814
+ mismatch.start === 0 ? s - softClipLength : s + mismatch.start
803
815
 
804
816
  for (let k = 0; k < softClipLength; k += 1) {
805
817
  const base = seq.charAt(k + mismatch.start)
@@ -136,9 +136,11 @@ function PileupRendering(props: {
136
136
  }
137
137
  let offsetX = 0
138
138
  let offsetY = 0
139
- if (highlightOverlayCanvas.current) {
140
- offsetX = highlightOverlayCanvas.current.getBoundingClientRect().left
141
- offsetY = highlightOverlayCanvas.current.getBoundingClientRect().top
139
+ const canvas = highlightOverlayCanvas.current
140
+ if (canvas) {
141
+ const { left, top } = canvas.getBoundingClientRect()
142
+ offsetX = left
143
+ offsetY = top
142
144
  }
143
145
  offsetX = event.clientX - offsetX
144
146
  offsetY = event.clientY - offsetY