@jbrowse/plugin-alignments 2.3.1 → 2.3.2

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 (32) hide show
  1. package/dist/LinearReadArcsDisplay/configSchema.js +8 -0
  2. package/dist/LinearReadArcsDisplay/configSchema.js.map +1 -1
  3. package/dist/LinearReadArcsDisplay/drawFeats.d.ts +1 -0
  4. package/dist/LinearReadArcsDisplay/drawFeats.js +45 -37
  5. package/dist/LinearReadArcsDisplay/drawFeats.js.map +1 -1
  6. package/dist/LinearReadArcsDisplay/model.d.ts +46 -15
  7. package/dist/LinearReadArcsDisplay/model.js +42 -0
  8. package/dist/LinearReadArcsDisplay/model.js.map +1 -1
  9. package/dist/LinearReadCloudDisplay/drawFeats.js +18 -32
  10. package/dist/LinearReadCloudDisplay/drawFeats.js.map +1 -1
  11. package/dist/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
  12. package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +1 -1
  13. package/dist/LinearSNPCoverageDisplay/components/Tooltip.js.map +1 -1
  14. package/esm/LinearReadArcsDisplay/configSchema.js +8 -0
  15. package/esm/LinearReadArcsDisplay/configSchema.js.map +1 -1
  16. package/esm/LinearReadArcsDisplay/drawFeats.d.ts +1 -0
  17. package/esm/LinearReadArcsDisplay/drawFeats.js +45 -37
  18. package/esm/LinearReadArcsDisplay/drawFeats.js.map +1 -1
  19. package/esm/LinearReadArcsDisplay/model.d.ts +46 -15
  20. package/esm/LinearReadArcsDisplay/model.js +42 -0
  21. package/esm/LinearReadArcsDisplay/model.js.map +1 -1
  22. package/esm/LinearReadCloudDisplay/drawFeats.js +19 -33
  23. package/esm/LinearReadCloudDisplay/drawFeats.js.map +1 -1
  24. package/esm/LinearSNPCoverageDisplay/components/Tooltip.d.ts +1 -1
  25. package/esm/LinearSNPCoverageDisplay/components/Tooltip.js +1 -1
  26. package/esm/LinearSNPCoverageDisplay/components/Tooltip.js.map +1 -1
  27. package/package.json +2 -2
  28. package/src/LinearReadArcsDisplay/configSchema.ts +10 -0
  29. package/src/LinearReadArcsDisplay/drawFeats.ts +73 -54
  30. package/src/LinearReadArcsDisplay/model.tsx +45 -0
  31. package/src/LinearReadCloudDisplay/drawFeats.ts +21 -33
  32. package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +76 -73
@@ -9,6 +9,7 @@ import {
9
9
  } from '../shared/color'
10
10
  import { ChainData } from '../shared/fetchChains'
11
11
  import { featurizeSA } from '../MismatchParser'
12
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
12
13
 
13
14
  export function hasPairedReads(features: ChainData) {
14
15
  for (const f of features.chains.values()) {
@@ -21,6 +22,17 @@ export function hasPairedReads(features: ChainData) {
21
22
 
22
23
  type LGV = LinearGenomeViewModel
23
24
 
25
+ function jitter(n: number) {
26
+ return Math.random() * 2 * n - n
27
+ }
28
+
29
+ interface CoreFeat {
30
+ strand: number
31
+ refName: string
32
+ start: number
33
+ end: number
34
+ }
35
+
24
36
  export default async function drawFeats(
25
37
  self: {
26
38
  setLastDrawnOffsetPx: (n: number) => void
@@ -31,34 +43,48 @@ export default async function drawFeats(
31
43
  height: number
32
44
  chainData?: ChainData
33
45
  lineWidthSetting: number
46
+ jitterVal: number
34
47
  },
35
48
  ctx: CanvasRenderingContext2D,
36
49
  ) {
37
- const { chainData } = self
50
+ const {
51
+ chainData,
52
+ height,
53
+ colorBy,
54
+ drawInter,
55
+ drawLongRange,
56
+ lineWidthSetting,
57
+ jitterVal,
58
+ } = self
38
59
  if (!chainData) {
39
60
  return
40
61
  }
41
- const displayHeight = self.height
42
62
  const view = getContainingView(self) as LGV
43
63
  const { assemblyManager } = getSession(self)
44
64
  self.setLastDrawnOffsetPx(view.offsetPx)
45
- ctx.lineWidth = self.lineWidthSetting
65
+ ctx.lineWidth = lineWidthSetting
46
66
  const { chains, stats } = chainData
47
67
  const hasPaired = hasPairedReads(chainData)
48
68
  const assemblyName = view.assemblyNames[0]
49
69
  const asm = assemblyManager.get(assemblyName)
50
- const type = self.colorBy?.type || 'insertSizeAndOrientation'
70
+ const type = colorBy?.type || 'insertSizeAndOrientation'
71
+ if (!asm) {
72
+ return
73
+ }
74
+
75
+ function drawLineAtOffset(p: number, c: string) {
76
+ // draws a vertical line off to middle of nowhere if the second end not found
77
+ ctx.strokeStyle = c
78
+ ctx.beginPath()
79
+ ctx.moveTo(p, 0)
80
+ ctx.lineTo(p, height)
81
+ ctx.stroke()
82
+ }
51
83
 
52
84
  function draw(
53
- k1: {
54
- strand: number
55
- refName: string
56
- start: number
57
- end: number
58
- tlen?: number
59
- pair_orientation?: string
60
- },
61
- k2: { strand: number; refName: string; start: number; end: number },
85
+ k1: CoreFeat & { tlen?: number; pair_orientation?: string },
86
+ k2: CoreFeat,
87
+ assembly: Assembly,
62
88
  longRange?: boolean,
63
89
  ) {
64
90
  const s1 = k1.strand
@@ -68,14 +94,16 @@ export default async function drawFeats(
68
94
 
69
95
  const p1 = f1 ? k1.start : k1.end
70
96
  const p2 = hasPaired ? (f2 ? k2.start : k2.end) : f2 ? k2.end : k2.start
71
-
72
- const r1 = view.bpToPx({ refName: k1.refName, coord: p1 })
73
- const r2 = view.bpToPx({ refName: k2.refName, coord: p2 })
97
+ const ra1 = assembly.getCanonicalRefName(k1.refName)
98
+ const ra2 = assembly.getCanonicalRefName(k2.refName)
99
+ const r1 = view.bpToPx({ refName: ra1, coord: p1 })
100
+ const r2 = view.bpToPx({ refName: ra2, coord: p2 })
74
101
 
75
102
  if (r1 && r2) {
76
103
  const radius = (r2.offsetPx - r1.offsetPx) / 2
77
104
  const absrad = Math.abs(radius)
78
105
  const p = r1.offsetPx - view.offsetPx
106
+ const p2 = r2.offsetPx - view.offsetPx
79
107
 
80
108
  // bezier (used for non-long-range arcs) requires moveTo before beginPath
81
109
  // arc (used for long-range) requires moveTo after beginPath (or else a
@@ -99,9 +127,7 @@ export default async function drawFeats(
99
127
  } else if (type === 'insertSize') {
100
128
  ctx.strokeStyle = getInsertSizeColor(k1, k2, stats) || 'grey'
101
129
  } else if (type === 'gradient') {
102
- ctx.strokeStyle = `hsl(${
103
- Math.log10(Math.abs(p1 - p2)) * 10
104
- },50%,50%)`
130
+ ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
105
131
  }
106
132
  } else {
107
133
  if (type === 'orientation' || type === 'insertSizeAndOrientation') {
@@ -113,53 +139,55 @@ export default async function drawFeats(
113
139
  ctx.strokeStyle = 'grey'
114
140
  }
115
141
  } else if (type === 'gradient') {
116
- ctx.strokeStyle = `hsl(${
117
- Math.log10(Math.abs(p1 - p2)) * 10
118
- },50%,50%)`
142
+ ctx.strokeStyle = `hsl(${Math.log10(absrad) * 10},50%,50%)`
119
143
  }
120
144
  }
121
145
  }
122
146
 
123
147
  const destX = p + radius * 2
124
- const destY = Math.min(displayHeight, absrad)
148
+ const destY = Math.min(height + jitter(jitterVal), absrad)
125
149
  if (longRange) {
126
- ctx.arc(p + radius, 0, absrad, 0, Math.PI)
150
+ // avoid drawing gigantic circles that glitch out the rendering,
151
+ // instead draw vertical lines
152
+ if (absrad > 100_000) {
153
+ drawLineAtOffset(p + jitter(jitterVal), 'red')
154
+ drawLineAtOffset(p2 + jitter(jitterVal), 'red')
155
+ } else {
156
+ ctx.arc(p + radius + jitter(jitterVal), 0, absrad, 0, Math.PI)
157
+ ctx.stroke()
158
+ }
127
159
  } else {
128
- ctx.bezierCurveTo(p, destY, destX, destY, destX, 0)
160
+ ctx.bezierCurveTo(
161
+ p + jitter(jitterVal),
162
+ destY,
163
+ destX,
164
+ destY,
165
+ destX + jitter(jitterVal),
166
+ 0,
167
+ )
168
+ ctx.stroke()
129
169
  }
130
- ctx.stroke()
131
- } else if (r1 && self.drawInter) {
132
- // draws a vertical line off to middle of nowhere if the second end not found
133
- const p = r1.offsetPx - view.offsetPx
134
- ctx.strokeStyle = 'purple'
135
- ctx.beginPath()
136
- ctx.moveTo(p, 0)
137
- ctx.lineTo(p, displayHeight)
138
- ctx.stroke()
170
+ } else if (r1 && drawInter) {
171
+ drawLineAtOffset(r1.offsetPx - view.offsetPx, 'purple')
139
172
  }
140
173
  }
141
174
 
142
175
  for (let i = 0; i < chains.length; i++) {
143
176
  let chain = chains[i]
144
-
145
- if (chain.length === 1 && self.drawLongRange) {
177
+ if (chain.length === 1 && drawLongRange) {
146
178
  // singleton feature
147
179
  const f = chain[0]
148
180
 
149
181
  // special case where we look at RPOS/RNEXT
150
182
  if (hasPaired) {
151
183
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
152
- const refName = asm?.getCanonicalRefName(f.next_ref!) || f.next_ref!
184
+ const refName = f.next_ref!
153
185
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154
186
  const coord = f.next_pos!
155
187
  draw(
156
188
  f,
157
- {
158
- refName,
159
- start: coord,
160
- end: coord,
161
- strand: f.strand,
162
- },
189
+ { refName, start: coord, end: coord, strand: f.strand },
190
+ asm,
163
191
  true,
164
192
  )
165
193
  }
@@ -171,16 +199,7 @@ export default async function drawFeats(
171
199
  for (let i = 0; i < features.length - 1; i++) {
172
200
  const f = features[i]
173
201
  const v1 = features[i + 1]
174
- draw(
175
- f,
176
- {
177
- refName: asm?.getCanonicalRefName(v1.refName) || v1.refName,
178
- start: v1.start,
179
- end: v1.end,
180
- strand: v1.strand,
181
- },
182
- true,
183
- )
202
+ draw(f, v1, asm, true)
184
203
  }
185
204
  }
186
205
  } else {
@@ -192,7 +211,7 @@ export default async function drawFeats(
192
211
  chain = chain.filter(f => !(f.flags & 2048))
193
212
  }
194
213
  for (let i = 0; i < chain.length - 1; i++) {
195
- draw(chain[i], chain[i + 1], false)
214
+ draw(chain[i], chain[i + 1], asm, false)
196
215
  }
197
216
  }
198
217
  }
@@ -65,6 +65,11 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
65
65
  */
66
66
  lineWidth: types.maybe(types.number),
67
67
 
68
+ /**
69
+ * #property
70
+ */
71
+ jitter: types.maybe(types.number),
72
+
68
73
  /**
69
74
  * #property
70
75
  */
@@ -175,6 +180,15 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
175
180
  setLineWidth(n: number) {
176
181
  self.lineWidth = n
177
182
  },
183
+
184
+ /**
185
+ * #action
186
+ * jitter val, helpful to jitter the x direction so you see better evidence when e.g. 100
187
+ * long reads map to same x position
188
+ */
189
+ setJitter(n: number) {
190
+ self.jitter = n
191
+ },
178
192
  }))
179
193
 
180
194
  .views(self => {
@@ -190,6 +204,13 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
190
204
  get lineWidthSetting() {
191
205
  return self.lineWidth ?? getConf(self, 'lineWidth')
192
206
  },
207
+
208
+ /**
209
+ * #getter
210
+ */
211
+ get jitterVal(): number {
212
+ return self.jitter ?? getConf(self, 'jitter')
213
+ },
193
214
  /**
194
215
  * #getter
195
216
  */
@@ -245,6 +266,30 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
245
266
  },
246
267
  ],
247
268
  },
269
+ {
270
+ label: 'Jitter x-positions',
271
+ subMenu: [
272
+ {
273
+ type: 'checkbox',
274
+ checked: this.jitterVal === 0,
275
+ label: 'None',
276
+ onClick: () => self.setJitter(0),
277
+ },
278
+ {
279
+ type: 'checkbox',
280
+ checked: this.jitterVal === 2,
281
+ label: 'Small',
282
+ onClick: () => self.setJitter(2),
283
+ },
284
+ {
285
+ type: 'checkbox',
286
+ checked: this.jitterVal === 10,
287
+ label: 'Large',
288
+ onClick: () => self.setJitter(10),
289
+ },
290
+ ],
291
+ },
292
+
248
293
  {
249
294
  label: 'Draw inter-region vertical lines',
250
295
  type: 'checkbox',
@@ -1,5 +1,5 @@
1
1
  import { getConf } from '@jbrowse/core/configuration'
2
- import { getContainingView } from '@jbrowse/core/util'
2
+ import { getContainingView, getSession } from '@jbrowse/core/util'
3
3
 
4
4
  import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
5
5
  // locals
@@ -60,9 +60,15 @@ export default async function drawFeats(
60
60
  if (!chainData) {
61
61
  return
62
62
  }
63
+ const { assemblyManager } = getSession(self)
63
64
  const featureHeight = getConf(self, 'featureHeight')
64
65
  const displayHeight = self.height
65
66
  const view = getContainingView(self) as LGV
67
+ const assemblyName = view.assemblyNames[0]
68
+ const asm = assemblyManager.get(assemblyName)
69
+ if (!asm) {
70
+ return
71
+ }
66
72
 
67
73
  self.setLastDrawnOffsetPx(view.offsetPx)
68
74
 
@@ -76,22 +82,13 @@ export default async function drawFeats(
76
82
  if (chain[0].flags & 1 && chain.length > 1) {
77
83
  const v0 = chain[0]
78
84
  const v1 = chain[1]
79
- const r1s = view.bpToPx({
80
- refName: v0.refName,
81
- coord: v0.start,
82
- })
83
- const r1e = view.bpToPx({
84
- refName: v0.refName,
85
- coord: v0.end,
86
- })
87
- const r2s = view.bpToPx({
88
- refName: v1.refName,
89
- coord: v1.start,
90
- })
91
- const r2e = view.bpToPx({
92
- refName: v1.refName,
93
- coord: v1.end,
94
- })
85
+ const ra1 = asm.getCanonicalRefName(v0.refName)
86
+ const ra2 = asm.getCanonicalRefName(v1.refName)
87
+ const r1s = view.bpToPx({ refName: ra1, coord: v0.start })
88
+ const r1e = view.bpToPx({ refName: ra1, coord: v0.end })
89
+ const r2s = view.bpToPx({ refName: ra2, coord: v1.start })
90
+ const r2e = view.bpToPx({ refName: ra2, coord: v1.end })
91
+
95
92
  let distance = 0
96
93
 
97
94
  if (
@@ -121,22 +118,13 @@ export default async function drawFeats(
121
118
  for (let i = 1; i < chain.length; i++) {
122
119
  const v0 = chain[i - 1]
123
120
  const v1 = chain[i]
124
- const r1s = view.bpToPx({
125
- refName: v0.refName,
126
- coord: v0.start,
127
- })
128
- const r1e = view.bpToPx({
129
- refName: v0.refName,
130
- coord: v0.end,
131
- })
132
- const r2s = view.bpToPx({
133
- refName: v1.refName,
134
- coord: v1.start,
135
- })
136
- const r2e = view.bpToPx({
137
- refName: v1.refName,
138
- coord: v1.end,
139
- })
121
+ const ra1 = asm.getCanonicalRefName(v0.refName)
122
+ const ra2 = asm.getCanonicalRefName(v1.refName)
123
+ const r1s = view.bpToPx({ refName: ra1, coord: v0.start })
124
+ const r1e = view.bpToPx({ refName: ra1, coord: v0.end })
125
+ const r2s = view.bpToPx({ refName: ra2, coord: v1.start })
126
+ const r2e = view.bpToPx({ refName: ra2, coord: v1.end })
127
+
140
128
  let distance = 0
141
129
 
142
130
  if (
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
- import { Feature } from '@jbrowse/core/util/simpleFeature'
3
+ import { Feature } from '@jbrowse/core/util'
4
4
  import { Tooltip } from '@jbrowse/plugin-wiggle'
5
5
 
6
6
  type Count = {
@@ -29,80 +29,83 @@ type SNPInfo = {
29
29
  const en = (n: number) => n.toLocaleString('en-US')
30
30
  const toP = (s = 0) => +(+s).toFixed(1)
31
31
  const pct = (n: number, total: number) => `${toP((n / (total || 1)) * 100)}%`
32
+ interface Props {
33
+ feature: Feature
34
+ }
35
+ const TooltipContents = React.forwardRef<HTMLDivElement, Props>(function (
36
+ { feature },
37
+ reactRef,
38
+ ) {
39
+ const start = feature.get('start')
40
+ const end = feature.get('end')
41
+ const name = feature.get('refName')
42
+ const {
43
+ refbase,
44
+ all,
45
+ total,
46
+ ref,
47
+ '-1': rn1,
48
+ '1': r1,
49
+ '0': r0,
50
+ ...info
51
+ } = feature.get('snpinfo') as SNPInfo
52
+ const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
53
+ .filter(f => !!f)
54
+ .join(':')
32
55
 
33
- const TooltipContents = React.forwardRef<HTMLDivElement, { feature: Feature }>(
34
- ({ feature }, reactRef) => {
35
- const start = feature.get('start')
36
- const end = feature.get('end')
37
- const name = feature.get('refName')
38
- const {
39
- refbase,
40
- all,
41
- total,
42
- ref,
43
- '-1': rn1,
44
- '1': r1,
45
- '0': r0,
46
- ...info
47
- } = feature.get('snpinfo') as SNPInfo
48
- const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
49
- .filter(f => !!f)
50
- .join(':')
51
-
52
- return (
53
- <div ref={reactRef}>
54
- <table>
55
- <caption>{loc}</caption>
56
- <thead>
57
- <tr>
58
- <th>Base</th>
59
- <th>Count</th>
60
- <th>% of Total</th>
61
- <th>Strands</th>
62
- <th>Source</th>
63
- </tr>
64
- </thead>
65
- <tbody>
66
- <tr>
67
- <td>Total</td>
68
- <td>{all}</td>
69
- </tr>
70
- <tr>
71
- <td>REF {refbase ? `(${refbase.toUpperCase()})` : ''}</td>
72
- <td>{ref}</td>
73
- <td>{pct(ref, all)}</td>
74
- <td>
75
- {rn1 ? `${rn1}(-)` : ''}
76
- {r1 ? `${r1}(+)` : ''}
77
- </td>
78
- <td />
79
- </tr>
56
+ return (
57
+ <div ref={reactRef}>
58
+ <table>
59
+ <caption>{loc}</caption>
60
+ <thead>
61
+ <tr>
62
+ <th>Base</th>
63
+ <th>Count</th>
64
+ <th>% of Total</th>
65
+ <th>Strands</th>
66
+ <th>Source</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody>
70
+ <tr>
71
+ <td>Total</td>
72
+ <td>{all}</td>
73
+ </tr>
74
+ <tr>
75
+ <td>REF {refbase ? `(${refbase.toUpperCase()})` : ''}</td>
76
+ <td>{ref}</td>
77
+ <td>{pct(ref, all)}</td>
78
+ <td>
79
+ {rn1 ? `${rn1}(-)` : ''}
80
+ {r1 ? `${r1}(+)` : ''}
81
+ </td>
82
+ <td />
83
+ </tr>
80
84
 
81
- {Object.entries(info as unknown as Record<string, Count>).map(
82
- ([key, entry]) =>
83
- Object.entries(entry).map(([base, score]) => (
84
- <tr key={base}>
85
- <td>{base.toUpperCase()}</td>
86
- <td>{score.total}</td>
87
- <td>
88
- {base === 'total' || base === 'skip'
89
- ? '---'
90
- : pct(score.total, all)}
91
- </td>
92
- <td>
93
- {score['-1'] ? `${score['-1']}(-)` : ''}
94
- {score['1'] ? `${score['1']}(+)` : ''}
95
- </td>
96
- <td>{key}</td>
97
- </tr>
98
- )),
99
- )}
100
- </tbody>
101
- </table>
102
- </div>
103
- )
104
- },
105
- )
85
+ {Object.entries(info as unknown as Record<string, Count>).map(
86
+ ([key, entry]) =>
87
+ Object.entries(entry).map(([base, score]) => (
88
+ <tr key={base}>
89
+ <td>{base.toUpperCase()}</td>
90
+ <td>{score.total}</td>
91
+ <td>
92
+ {base === 'total' || base === 'skip'
93
+ ? '---'
94
+ : pct(score.total, all)}
95
+ </td>
96
+ <td>
97
+ {score['-1'] ? `${score['-1']}(-)` : ''}
98
+ {score['1'] ? `${score['1']}(+)` : ''}
99
+ </td>
100
+ <td>{key}</td>
101
+ </tr>
102
+ )),
103
+ )}
104
+ </tbody>
105
+ </table>
106
+ </div>
107
+ )
108
+ })
106
109
 
107
110
  type Coord = [number, number]
108
111