@jbrowse/plugin-alignments 1.7.9 → 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 +11 -2
- package/dist/AlignmentsFeatureDetail/index.d.ts +28 -3
- package/dist/AlignmentsFeatureDetail/index.js +6 -17
- package/dist/BamAdapter/MismatchParser.js +1 -0
- package/dist/LinearPileupDisplay/model.d.ts +5 -2
- package/dist/LinearPileupDisplay/model.js +131 -50
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +35 -14
- package/dist/PileupRenderer/PileupRenderer.d.ts +53 -8
- package/dist/PileupRenderer/PileupRenderer.js +197 -122
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +2 -0
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +3 -0
- package/package.json +2 -2
- package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +12 -2
- package/src/AlignmentsFeatureDetail/index.ts +7 -17
- package/src/BamAdapter/MismatchParser.ts +1 -0
- package/src/LinearPileupDisplay/model.ts +75 -23
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +44 -30
- package/src/PileupRenderer/PileupRenderer.tsx +364 -177
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +5 -0
|
@@ -388,6 +388,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
|
|
|
388
388
|
if (bins[_i] === undefined) {
|
|
389
389
|
bins[_i] = {
|
|
390
390
|
total: 0,
|
|
391
|
+
all: 0,
|
|
391
392
|
ref: 0,
|
|
392
393
|
'-1': 0,
|
|
393
394
|
'0': 0,
|
|
@@ -401,6 +402,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
|
|
|
401
402
|
|
|
402
403
|
if (j !== fend) {
|
|
403
404
|
bins[_i].total++;
|
|
405
|
+
bins[_i].all++;
|
|
404
406
|
bins[_i].ref++;
|
|
405
407
|
bins[_i][fstrand]++;
|
|
406
408
|
}
|
|
@@ -547,6 +549,7 @@ var SNPCoverageAdapter = /*#__PURE__*/function (_BaseFeatureDataAdapt) {
|
|
|
547
549
|
_bin.total--;
|
|
548
550
|
} else if (!interbase && colorSNPs) {
|
|
549
551
|
inc(_bin, fstrand, 'cov', base);
|
|
552
|
+
_bin.refbase = mismatch.altbase;
|
|
550
553
|
}
|
|
551
554
|
}
|
|
552
555
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-alignments",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10",
|
|
4
4
|
"description": "JBrowse 2 alignments adapters, tracks, etc.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "02d8c1e88e5603ea5855faed4ccb814e28071b32"
|
|
61
61
|
}
|
|
@@ -77,12 +77,22 @@ function AlignmentFlags(props: { feature: any }) {
|
|
|
77
77
|
|
|
78
78
|
function Formatter({ value }: { value: unknown }) {
|
|
79
79
|
const [show, setShow] = useState(false)
|
|
80
|
+
const [copied, setCopied] = useState(false)
|
|
80
81
|
const display = String(value)
|
|
81
82
|
if (display.length > 100) {
|
|
82
83
|
return (
|
|
83
84
|
<>
|
|
84
|
-
<button
|
|
85
|
-
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={() => {
|
|
88
|
+
copy(display)
|
|
89
|
+
setCopied(true)
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
setCopied(false)
|
|
92
|
+
}, 700)
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{copied ? 'Copied to clipboard' : 'Copy'}
|
|
86
96
|
</button>
|
|
87
97
|
<button type="button" onClick={() => setShow(val => !val)}>
|
|
88
98
|
{show ? 'Show less' : 'Show more'}
|
|
@@ -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) {
|
|
@@ -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>
|