@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.
- 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/MismatchParser.js +4 -2
- package/dist/LinearPileupDisplay/model.d.ts +7 -4
- package/dist/LinearPileupDisplay/model.js +131 -50
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +35 -14
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +3 -3
- package/dist/PileupRenderer/PileupRenderer.d.ts +66 -9
- package/dist/PileupRenderer/PileupRenderer.js +199 -124
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +2 -0
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +3 -0
- package/dist/SNPCoverageRenderer/configSchema.d.ts +1 -1
- package/package.json +2 -2
- package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +14 -3
- package/src/AlignmentsFeatureDetail/index.ts +7 -17
- package/src/BamAdapter/MismatchParser.ts +4 -2
- package/src/LinearPileupDisplay/model.ts +75 -23
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +44 -30
- package/src/PileupRenderer/PileupRenderer.tsx +366 -180
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +5 -0
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import { lazy } from 'react'
|
|
2
2
|
import PluginManager from '@jbrowse/core/PluginManager'
|
|
3
3
|
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
4
|
-
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
5
4
|
import { types } from 'mobx-state-tree'
|
|
6
5
|
import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
|
|
6
|
+
import { stateModelFactory as baseModelFactory } from '@jbrowse/core/BaseFeatureWidget'
|
|
7
7
|
|
|
8
8
|
const configSchema = ConfigurationSchema('AlignmentsFeatureWidget', {})
|
|
9
9
|
|
|
10
10
|
export function stateModelFactory(pluginManager: PluginManager) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const baseModel = baseModelFactory(pluginManager)
|
|
12
|
+
return types.compose(
|
|
13
|
+
baseModel,
|
|
14
|
+
types.model('AlignmentsFeatureWidget', {
|
|
14
15
|
type: types.literal('AlignmentsFeatureWidget'),
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
pluginManager.pluggableMstType('view', 'stateModel'),
|
|
18
|
-
),
|
|
19
|
-
})
|
|
20
|
-
.actions(self => ({
|
|
21
|
-
setFeatureData(data: unknown) {
|
|
22
|
-
self.featureData = data
|
|
23
|
-
},
|
|
24
|
-
clearFeatureData() {
|
|
25
|
-
self.featureData = undefined
|
|
26
|
-
},
|
|
27
|
-
}))
|
|
16
|
+
}),
|
|
17
|
+
)
|
|
28
18
|
}
|
|
29
19
|
|
|
30
20
|
export default function register(pluginManager: PluginManager) {
|
|
@@ -10,6 +10,7 @@ export interface Mismatch {
|
|
|
10
10
|
cliplen?: number
|
|
11
11
|
}
|
|
12
12
|
const mdRegex = new RegExp(/(\d+|\^[a-z]+|[a-z])/gi)
|
|
13
|
+
const modificationRegex = new RegExp(/([A-Z])([-+])([^,.?]+)([.?])?/)
|
|
13
14
|
export function parseCigar(cigar: string) {
|
|
14
15
|
return (cigar || '').split(/([MIDNSHPX=])/)
|
|
15
16
|
}
|
|
@@ -38,6 +39,7 @@ export function cigarToMismatches(
|
|
|
38
39
|
start: roffset + j,
|
|
39
40
|
type: 'mismatch',
|
|
40
41
|
base: seq[soffset + j],
|
|
42
|
+
altbase: ref[roffset + j],
|
|
41
43
|
length: 1,
|
|
42
44
|
})
|
|
43
45
|
}
|
|
@@ -273,7 +275,7 @@ export function getModificationPositions(
|
|
|
273
275
|
const [basemod, ...skips] = mod.split(',')
|
|
274
276
|
|
|
275
277
|
// regexes based on parse_mm.pl from hts-specs
|
|
276
|
-
const matches = basemod.match(
|
|
278
|
+
const matches = basemod.match(modificationRegex)
|
|
277
279
|
if (!matches) {
|
|
278
280
|
throw new Error('bad format for MM tag')
|
|
279
281
|
}
|
|
@@ -328,7 +330,7 @@ export function getModificationTypes(mm: string) {
|
|
|
328
330
|
.map(mod => {
|
|
329
331
|
const [basemod] = mod.split(',')
|
|
330
332
|
|
|
331
|
-
const matches = basemod.match(
|
|
333
|
+
const matches = basemod.match(modificationRegex)
|
|
332
334
|
if (!matches) {
|
|
333
335
|
throw new Error('bad format for MM tag')
|
|
334
336
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { lazy } from 'react'
|
|
2
|
+
import { autorun, observable } from 'mobx'
|
|
3
|
+
import { cast, types, addDisposer, getEnv, Instance } from 'mobx-state-tree'
|
|
4
|
+
import copy from 'copy-to-clipboard'
|
|
2
5
|
import {
|
|
6
|
+
AnyConfigurationModel,
|
|
3
7
|
ConfigurationReference,
|
|
4
8
|
readConfObject,
|
|
5
9
|
getConf,
|
|
@@ -13,26 +17,25 @@ import {
|
|
|
13
17
|
Feature,
|
|
14
18
|
} from '@jbrowse/core/util'
|
|
15
19
|
|
|
16
|
-
import VisibilityIcon from '@material-ui/icons/Visibility'
|
|
17
|
-
import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons'
|
|
18
20
|
import {
|
|
19
21
|
LinearGenomeViewModel,
|
|
20
22
|
BaseLinearDisplay,
|
|
21
23
|
} from '@jbrowse/plugin-linear-genome-view'
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
// icons
|
|
26
|
+
import VisibilityIcon from '@material-ui/icons/Visibility'
|
|
27
|
+
import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons'
|
|
24
28
|
import MenuOpenIcon from '@material-ui/icons/MenuOpen'
|
|
25
29
|
import SortIcon from '@material-ui/icons/Sort'
|
|
26
30
|
import PaletteIcon from '@material-ui/icons/Palette'
|
|
27
31
|
import FilterListIcon from '@material-ui/icons/ClearAll'
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
|
|
33
|
+
// locals
|
|
31
34
|
import { LinearPileupDisplayConfigModel } from './configSchema'
|
|
32
35
|
import LinearPileupDisplayBlurb from './components/LinearPileupDisplayBlurb'
|
|
33
|
-
|
|
34
36
|
import { getUniqueTagValues, getUniqueModificationValues } from '../shared'
|
|
35
37
|
|
|
38
|
+
// async
|
|
36
39
|
const ColorByTagDlg = lazy(() => import('./components/ColorByTag'))
|
|
37
40
|
const FilterByTagDlg = lazy(() => import('./components/FilterByTag'))
|
|
38
41
|
const SortByTagDlg = lazy(() => import('./components/SortByTag'))
|
|
@@ -94,6 +97,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
94
97
|
.volatile(() => ({
|
|
95
98
|
colorTagMap: observable.map<string, string>({}),
|
|
96
99
|
modificationTagMap: observable.map<string, string>({}),
|
|
100
|
+
featureUnderMouseVolatile: undefined as undefined | Feature,
|
|
97
101
|
ready: false,
|
|
98
102
|
}))
|
|
99
103
|
.actions(self => ({
|
|
@@ -128,7 +132,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
128
132
|
},
|
|
129
133
|
|
|
130
134
|
updateColorTagMap(uniqueTag: string[]) {
|
|
131
|
-
// pale color scheme
|
|
135
|
+
// pale color scheme
|
|
136
|
+
// https://cran.r-project.org/web/packages/khroma/vignettes/tol.html
|
|
137
|
+
// e.g. "tol_light"
|
|
132
138
|
const colorPalette = [
|
|
133
139
|
'#BBCCEE',
|
|
134
140
|
'pink',
|
|
@@ -150,6 +156,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
150
156
|
}
|
|
151
157
|
})
|
|
152
158
|
},
|
|
159
|
+
setFeatureUnderMouse(feat?: Feature) {
|
|
160
|
+
self.featureUnderMouseVolatile = feat
|
|
161
|
+
},
|
|
153
162
|
}))
|
|
154
163
|
.actions(self => ({
|
|
155
164
|
afterAttach() {
|
|
@@ -226,6 +235,48 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
226
235
|
{ delay: 1000 },
|
|
227
236
|
),
|
|
228
237
|
)
|
|
238
|
+
|
|
239
|
+
// autorun synchronizes featureUnderMouse with featureIdUnderMouse
|
|
240
|
+
addDisposer(
|
|
241
|
+
self,
|
|
242
|
+
autorun(async () => {
|
|
243
|
+
const session = getSession(self)
|
|
244
|
+
try {
|
|
245
|
+
const featureId = self.featureIdUnderMouse
|
|
246
|
+
if (self.featureUnderMouse?.id() !== featureId) {
|
|
247
|
+
if (!featureId) {
|
|
248
|
+
self.setFeatureUnderMouse(undefined)
|
|
249
|
+
} else {
|
|
250
|
+
const sessionId = getRpcSessionId(self)
|
|
251
|
+
const view = getContainingView(self)
|
|
252
|
+
const { feature } = (await session.rpcManager.call(
|
|
253
|
+
sessionId,
|
|
254
|
+
'CoreGetFeatureDetails',
|
|
255
|
+
{
|
|
256
|
+
featureId,
|
|
257
|
+
sessionId,
|
|
258
|
+
layoutId: view.id,
|
|
259
|
+
rendererType: 'PileupRenderer',
|
|
260
|
+
},
|
|
261
|
+
)) as { feature: unknown }
|
|
262
|
+
|
|
263
|
+
// check featureIdUnderMouse is still the same as the
|
|
264
|
+
// feature.id that was returned e.g. that the user hasn't
|
|
265
|
+
// moused over to a new position during the async operation
|
|
266
|
+
// above
|
|
267
|
+
// @ts-ignore
|
|
268
|
+
if (self.featureIdUnderMouse === feature.uniqueId) {
|
|
269
|
+
// @ts-ignore
|
|
270
|
+
self.setFeatureUnderMouse(new SimpleFeature(feature))
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.error(e)
|
|
276
|
+
session.notify(`${e}`, 'error')
|
|
277
|
+
}
|
|
278
|
+
}),
|
|
279
|
+
)
|
|
229
280
|
},
|
|
230
281
|
selectFeature(feature: Feature) {
|
|
231
282
|
const session = getSession(self)
|
|
@@ -337,6 +388,9 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
337
388
|
? self.mismatchAlpha
|
|
338
389
|
: readConfObject(this.rendererConfig, 'mismatchAlpha')
|
|
339
390
|
},
|
|
391
|
+
get featureUnderMouse() {
|
|
392
|
+
return self.featureUnderMouseVolatile
|
|
393
|
+
},
|
|
340
394
|
}))
|
|
341
395
|
.views(self => {
|
|
342
396
|
const {
|
|
@@ -395,6 +449,8 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
395
449
|
colorBy,
|
|
396
450
|
filterBy,
|
|
397
451
|
rpcDriverName,
|
|
452
|
+
currBpPerPx,
|
|
453
|
+
ready,
|
|
398
454
|
} = self
|
|
399
455
|
|
|
400
456
|
const superProps = superRenderProps()
|
|
@@ -403,8 +459,8 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
403
459
|
...superProps,
|
|
404
460
|
notReady:
|
|
405
461
|
superProps.notReady ||
|
|
406
|
-
!
|
|
407
|
-
(sortedBy &&
|
|
462
|
+
!ready ||
|
|
463
|
+
(sortedBy && currBpPerPx !== view.bpPerPx),
|
|
408
464
|
rpcDriverName,
|
|
409
465
|
displayModel: self,
|
|
410
466
|
sortedBy,
|
|
@@ -414,7 +470,7 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
414
470
|
modificationTagMap: JSON.parse(JSON.stringify(modificationTagMap)),
|
|
415
471
|
showSoftClip: self.showSoftClipping,
|
|
416
472
|
config: self.rendererConfig,
|
|
417
|
-
async onFeatureClick(_: unknown, featureId
|
|
473
|
+
async onFeatureClick(_: unknown, featureId?: string) {
|
|
418
474
|
const session = getSession(self)
|
|
419
475
|
const { rpcManager } = session
|
|
420
476
|
try {
|
|
@@ -444,14 +500,12 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
444
500
|
session.notify(`${e}`)
|
|
445
501
|
}
|
|
446
502
|
},
|
|
503
|
+
|
|
447
504
|
onClick() {
|
|
448
505
|
self.clearFeatureSelection()
|
|
449
506
|
},
|
|
450
507
|
// similar to click but opens a menu with further options
|
|
451
|
-
async onFeatureContextMenu(
|
|
452
|
-
_: unknown,
|
|
453
|
-
featureId: string | undefined,
|
|
454
|
-
) {
|
|
508
|
+
async onFeatureContextMenu(_: unknown, featureId?: string) {
|
|
455
509
|
const session = getSession(self)
|
|
456
510
|
const { rpcManager } = session
|
|
457
511
|
try {
|
|
@@ -507,19 +561,17 @@ const stateModelFactory = (configSchema: LinearPileupDisplayConfigModel) =>
|
|
|
507
561
|
disabled: self.showSoftClipping,
|
|
508
562
|
subMenu: [
|
|
509
563
|
...['Start location', 'Read strand', 'Base pair'].map(
|
|
510
|
-
option => {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
},
|
|
564
|
+
option => ({
|
|
565
|
+
label: option,
|
|
566
|
+
onClick: () => self.setSortedBy(option),
|
|
567
|
+
}),
|
|
516
568
|
),
|
|
517
569
|
{
|
|
518
570
|
label: 'Sort by tag...',
|
|
519
571
|
onClick: () => {
|
|
520
|
-
getSession(self).queueDialog(
|
|
572
|
+
getSession(self).queueDialog(handleClose => [
|
|
521
573
|
SortByTagDlg,
|
|
522
|
-
{ model: self, handleClose
|
|
574
|
+
{ model: self, handleClose },
|
|
523
575
|
])
|
|
524
576
|
},
|
|
525
577
|
},
|
|
@@ -7,37 +7,51 @@ import { Tooltip } from '@jbrowse/plugin-wiggle'
|
|
|
7
7
|
type Count = {
|
|
8
8
|
[key: string]: {
|
|
9
9
|
total: number
|
|
10
|
+
'-1': number
|
|
11
|
+
'0': number
|
|
12
|
+
'1': number
|
|
10
13
|
}
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
type SNPInfo = {
|
|
14
|
-
ref: Count
|
|
15
17
|
cov: Count
|
|
16
18
|
lowqual: Count
|
|
17
19
|
noncov: Count
|
|
18
20
|
delskips: Count
|
|
21
|
+
refbase: string
|
|
19
22
|
total: number
|
|
23
|
+
ref: number
|
|
24
|
+
all: number
|
|
20
25
|
'-1': number
|
|
21
26
|
'0': number
|
|
22
27
|
'1': number
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
const en = (n: number) => n.toLocaleString('en-US')
|
|
31
|
+
const toP = (s = 0) => +(+s).toFixed(1)
|
|
32
|
+
const pct = (n: number, total: number) => `${toP((n / (total || 1)) * 100)}%`
|
|
26
33
|
|
|
27
34
|
const TooltipContents = React.forwardRef(
|
|
28
|
-
({ feature }: { feature: Feature },
|
|
35
|
+
({ feature }: { feature: Feature }, reactRef: any) => {
|
|
29
36
|
const start = feature.get('start')
|
|
30
37
|
const end = feature.get('end')
|
|
31
38
|
const name = feature.get('refName')
|
|
32
|
-
const
|
|
39
|
+
const {
|
|
40
|
+
refbase,
|
|
41
|
+
all,
|
|
42
|
+
total,
|
|
43
|
+
ref,
|
|
44
|
+
'-1': rn1,
|
|
45
|
+
'1': r1,
|
|
46
|
+
'0': r0,
|
|
47
|
+
...info
|
|
48
|
+
} = feature.get('snpinfo') as SNPInfo
|
|
33
49
|
const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
|
|
34
50
|
.filter(f => !!f)
|
|
35
51
|
.join(':')
|
|
36
52
|
|
|
37
|
-
const total = info?.total
|
|
38
|
-
|
|
39
53
|
return (
|
|
40
|
-
<div ref={
|
|
54
|
+
<div ref={reactRef}>
|
|
41
55
|
<table>
|
|
42
56
|
<caption>{loc}</caption>
|
|
43
57
|
<thead>
|
|
@@ -52,37 +66,37 @@ const TooltipContents = React.forwardRef(
|
|
|
52
66
|
<tbody>
|
|
53
67
|
<tr>
|
|
54
68
|
<td>Total</td>
|
|
55
|
-
<td>{
|
|
69
|
+
<td>{all}</td>
|
|
56
70
|
</tr>
|
|
57
71
|
<tr>
|
|
58
|
-
<td>REF</td>
|
|
59
|
-
<td>{
|
|
72
|
+
<td>REF {refbase ? `(${refbase.toUpperCase()})` : ''}</td>
|
|
73
|
+
<td>{ref}</td>
|
|
74
|
+
<td>{pct(ref, all)}</td>
|
|
60
75
|
<td>
|
|
61
|
-
{
|
|
62
|
-
{
|
|
76
|
+
{rn1 ? `${rn1}(-)` : ''}
|
|
77
|
+
{r1 ? `${r1}(+)` : ''}
|
|
63
78
|
</td>
|
|
64
79
|
<td />
|
|
65
80
|
</tr>
|
|
66
81
|
|
|
67
|
-
{Object.entries(info).map(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)),
|
|
82
|
+
{Object.entries(info as unknown as Record<string, Count>).map(
|
|
83
|
+
([key, entry]) =>
|
|
84
|
+
Object.entries(entry).map(([base, score]) => (
|
|
85
|
+
<tr key={base}>
|
|
86
|
+
<td>{base.toUpperCase()}</td>
|
|
87
|
+
<td>{score.total}</td>
|
|
88
|
+
<td>
|
|
89
|
+
{base === 'total' || base === 'skip'
|
|
90
|
+
? '---'
|
|
91
|
+
: pct(score.total, all)}
|
|
92
|
+
</td>
|
|
93
|
+
<td>
|
|
94
|
+
{score['-1'] ? `${score['-1']}(-)` : ''}
|
|
95
|
+
{score['1'] ? `${score['1']}(+)` : ''}
|
|
96
|
+
</td>
|
|
97
|
+
<td>{key}</td>
|
|
98
|
+
</tr>
|
|
99
|
+
)),
|
|
86
100
|
)}
|
|
87
101
|
</tbody>
|
|
88
102
|
</table>
|