@jbrowse/plugin-alignments 1.7.7 → 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 +13 -3
- package/dist/AlignmentsFeatureDetail/index.d.ts +28 -3
- package/dist/AlignmentsFeatureDetail/index.js +6 -17
- package/dist/BamAdapter/BamAdapter.d.ts +1 -1
- package/dist/BamAdapter/BamAdapter.js +3 -3
- package/dist/BamAdapter/MismatchParser.d.ts +2 -5
- package/dist/BamAdapter/MismatchParser.js +108 -46
- package/dist/BamAdapter/MismatchParser.test.js +6 -14
- package/dist/CramAdapter/CramAdapter.d.ts +10 -9
- package/dist/CramAdapter/CramAdapter.js +6 -6
- package/dist/CramAdapter/CramSlightlyLazyFeature.js +35 -30
- package/dist/LinearPileupDisplay/model.d.ts +6 -3
- package/dist/LinearPileupDisplay/model.js +132 -51
- package/dist/LinearSNPCoverageDisplay/components/Tooltip.js +37 -17
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
- package/dist/LinearSNPCoverageDisplay/models/model.js +1 -1
- package/dist/PileupRenderer/PileupLayoutSession.d.ts +3 -0
- package/dist/PileupRenderer/PileupLayoutSession.js +3 -1
- package/dist/PileupRenderer/PileupRenderer.d.ts +66 -9
- package/dist/PileupRenderer/PileupRenderer.js +296 -258
- package/dist/PileupRenderer/configSchema.js +2 -2
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +6 -6
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.js +95 -96
- package/dist/SNPCoverageRenderer/configSchema.d.ts +1 -1
- package/package.json +3 -3
- package/src/AlignmentsFeatureDetail/AlignmentsFeatureDetail.tsx +14 -3
- package/src/AlignmentsFeatureDetail/index.ts +7 -17
- package/src/BamAdapter/BamAdapter.ts +3 -3
- package/src/BamAdapter/MismatchParser.test.ts +5 -7
- package/src/BamAdapter/MismatchParser.ts +72 -59
- package/src/CramAdapter/CramAdapter.ts +14 -10
- package/src/CramAdapter/CramSlightlyLazyFeature.ts +84 -91
- package/src/LinearPileupDisplay/model.ts +76 -24
- package/src/LinearSNPCoverageDisplay/components/Tooltip.tsx +41 -20
- package/src/LinearSNPCoverageDisplay/models/model.ts +1 -1
- package/src/PileupRenderer/PileupLayoutSession.ts +6 -1
- package/src/PileupRenderer/PileupRenderer.tsx +413 -225
- package/src/PileupRenderer/configSchema.ts +2 -2
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +89 -76
|
@@ -20,6 +20,13 @@ interface Header {
|
|
|
20
20
|
readGroups?: number[]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
interface FilterBy {
|
|
24
|
+
flagInclude: number
|
|
25
|
+
flagExclude: number
|
|
26
|
+
tagFilter: { tag: string; value: unknown }
|
|
27
|
+
readName: string
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
export default class CramAdapter extends BaseFeatureDataAdapter {
|
|
24
31
|
samHeader: Header = {}
|
|
25
32
|
|
|
@@ -206,12 +213,7 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
|
|
|
206
213
|
getFeatures(
|
|
207
214
|
region: Region & { originalRefName?: string },
|
|
208
215
|
opts?: BaseOptions & {
|
|
209
|
-
filterBy:
|
|
210
|
-
flagInclude: number
|
|
211
|
-
flagExclude: number
|
|
212
|
-
tagFilter: { tag: string; value: unknown }
|
|
213
|
-
name: string
|
|
214
|
-
}
|
|
216
|
+
filterBy: FilterBy
|
|
215
217
|
},
|
|
216
218
|
) {
|
|
217
219
|
const { signal, filterBy, statusCallback = () => {} } = opts || {}
|
|
@@ -234,7 +236,7 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
|
|
|
234
236
|
flagInclude = 0,
|
|
235
237
|
flagExclude = 0,
|
|
236
238
|
tagFilter,
|
|
237
|
-
|
|
239
|
+
readName,
|
|
238
240
|
} = filterBy || {}
|
|
239
241
|
|
|
240
242
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -251,9 +253,11 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
|
|
|
251
253
|
})
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
if (readName) {
|
|
257
|
+
filtered = filtered.filter(
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
(record: any) => record.readName === readName,
|
|
260
|
+
)
|
|
257
261
|
}
|
|
258
262
|
|
|
259
263
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -131,7 +131,8 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
131
131
|
let sublen = 0
|
|
132
132
|
if (typeof this.record.readFeatures !== 'undefined') {
|
|
133
133
|
// @ts-ignore
|
|
134
|
-
this.record.readFeatures.
|
|
134
|
+
for (let i = 0; i < this.record.readFeatures.length; i++) {
|
|
135
|
+
const { code, refPos, sub, data } = this.record.readFeatures[i]
|
|
135
136
|
sublen = refPos - last_pos
|
|
136
137
|
seq += ref.substring(last_pos - refStart, refPos - refStart)
|
|
137
138
|
last_pos = refPos
|
|
@@ -200,7 +201,7 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
200
201
|
cigar += `${data}H`
|
|
201
202
|
oplen = 0
|
|
202
203
|
} // else q or Q
|
|
203
|
-
}
|
|
204
|
+
}
|
|
204
205
|
} else {
|
|
205
206
|
sublen = this.record.readLength - seq.length
|
|
206
207
|
}
|
|
@@ -303,95 +304,87 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
303
304
|
return []
|
|
304
305
|
}
|
|
305
306
|
const start = this.get('start')
|
|
306
|
-
const mismatches: Mismatch[] =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
mismatches.push({
|
|
322
|
-
start: refPos,
|
|
323
|
-
length: 1,
|
|
324
|
-
base: sub,
|
|
325
|
-
qual: qual?.[pos],
|
|
326
|
-
altbase: ref,
|
|
327
|
-
type: 'mismatch',
|
|
328
|
-
})
|
|
329
|
-
} else if (code === 'I') {
|
|
330
|
-
// insertion
|
|
331
|
-
mismatches.push({
|
|
332
|
-
start: refPos,
|
|
333
|
-
type: 'insertion',
|
|
334
|
-
base: `${data.length}`,
|
|
335
|
-
length: 0,
|
|
336
|
-
})
|
|
337
|
-
} else if (code === 'N') {
|
|
338
|
-
// reference skip
|
|
339
|
-
mismatches.push({
|
|
340
|
-
type: 'skip',
|
|
341
|
-
length: data,
|
|
342
|
-
start: refPos,
|
|
343
|
-
base: 'N',
|
|
344
|
-
})
|
|
345
|
-
} else if (code === 'S') {
|
|
346
|
-
// soft clip
|
|
347
|
-
const len = data.length
|
|
348
|
-
mismatches.push({
|
|
349
|
-
start: refPos,
|
|
350
|
-
type: 'softclip',
|
|
351
|
-
base: `S${len}`,
|
|
352
|
-
cliplen: len,
|
|
353
|
-
length: 1,
|
|
354
|
-
})
|
|
355
|
-
} else if (code === 'P') {
|
|
356
|
-
// padding
|
|
357
|
-
} else if (code === 'H') {
|
|
358
|
-
// hard clip
|
|
359
|
-
const len = data
|
|
360
|
-
mismatches.push({
|
|
361
|
-
start: refPos,
|
|
362
|
-
type: 'hardclip',
|
|
363
|
-
base: `H${len}`,
|
|
364
|
-
cliplen: len,
|
|
365
|
-
length: 1,
|
|
366
|
-
})
|
|
367
|
-
} else if (code === 'D') {
|
|
368
|
-
// deletion
|
|
369
|
-
mismatches.push({
|
|
370
|
-
type: 'deletion',
|
|
371
|
-
length: data,
|
|
372
|
-
start: refPos,
|
|
373
|
-
base: '*',
|
|
374
|
-
})
|
|
375
|
-
} else if (code === 'b') {
|
|
376
|
-
// stretch of bases
|
|
377
|
-
} else if (code === 'q') {
|
|
378
|
-
// stretch of qual scores
|
|
379
|
-
} else if (code === 'B') {
|
|
380
|
-
// a pair of [base, qual]
|
|
381
|
-
} else if (code === 'i') {
|
|
382
|
-
// single-base insertion
|
|
383
|
-
// insertion
|
|
384
|
-
mismatches.push({
|
|
385
|
-
start: refPos,
|
|
386
|
-
type: 'insertion',
|
|
387
|
-
base: data,
|
|
388
|
-
length: 1,
|
|
389
|
-
})
|
|
390
|
-
} else if (code === 'Q') {
|
|
391
|
-
// single quality value
|
|
307
|
+
const mismatches: Mismatch[] = new Array(readFeatures.length)
|
|
308
|
+
let j = 0
|
|
309
|
+
for (let i = 0; i < readFeatures.length; i++) {
|
|
310
|
+
const f = readFeatures[i]
|
|
311
|
+
const { code, pos, data, sub, ref } = f
|
|
312
|
+
const refPos = f.refPos - 1 - start
|
|
313
|
+
if (code === 'X') {
|
|
314
|
+
// substitution
|
|
315
|
+
mismatches[j++] = {
|
|
316
|
+
start: refPos,
|
|
317
|
+
length: 1,
|
|
318
|
+
base: sub,
|
|
319
|
+
qual: qual?.[pos],
|
|
320
|
+
altbase: ref,
|
|
321
|
+
type: 'mismatch',
|
|
392
322
|
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
323
|
+
} else if (code === 'I') {
|
|
324
|
+
// insertion
|
|
325
|
+
mismatches[j++] = {
|
|
326
|
+
start: refPos,
|
|
327
|
+
type: 'insertion',
|
|
328
|
+
base: `${data.length}`,
|
|
329
|
+
length: 0,
|
|
330
|
+
}
|
|
331
|
+
} else if (code === 'N') {
|
|
332
|
+
// reference skip
|
|
333
|
+
mismatches[j++] = {
|
|
334
|
+
type: 'skip',
|
|
335
|
+
length: data,
|
|
336
|
+
start: refPos,
|
|
337
|
+
base: 'N',
|
|
338
|
+
}
|
|
339
|
+
} else if (code === 'S') {
|
|
340
|
+
// soft clip
|
|
341
|
+
const len = data.length
|
|
342
|
+
mismatches[j++] = {
|
|
343
|
+
start: refPos,
|
|
344
|
+
type: 'softclip',
|
|
345
|
+
base: `S${len}`,
|
|
346
|
+
cliplen: len,
|
|
347
|
+
length: 1,
|
|
348
|
+
}
|
|
349
|
+
} else if (code === 'P') {
|
|
350
|
+
// padding
|
|
351
|
+
} else if (code === 'H') {
|
|
352
|
+
// hard clip
|
|
353
|
+
const len = data
|
|
354
|
+
mismatches[j++] = {
|
|
355
|
+
start: refPos,
|
|
356
|
+
type: 'hardclip',
|
|
357
|
+
base: `H${len}`,
|
|
358
|
+
cliplen: len,
|
|
359
|
+
length: 1,
|
|
360
|
+
}
|
|
361
|
+
} else if (code === 'D') {
|
|
362
|
+
// deletion
|
|
363
|
+
mismatches[j++] = {
|
|
364
|
+
type: 'deletion',
|
|
365
|
+
length: data,
|
|
366
|
+
start: refPos,
|
|
367
|
+
base: '*',
|
|
368
|
+
}
|
|
369
|
+
} else if (code === 'b') {
|
|
370
|
+
// stretch of bases
|
|
371
|
+
} else if (code === 'q') {
|
|
372
|
+
// stretch of qual scores
|
|
373
|
+
} else if (code === 'B') {
|
|
374
|
+
// a pair of [base, qual]
|
|
375
|
+
} else if (code === 'i') {
|
|
376
|
+
// single-base insertion
|
|
377
|
+
// insertion
|
|
378
|
+
mismatches[j++] = {
|
|
379
|
+
start: refPos,
|
|
380
|
+
type: 'insertion',
|
|
381
|
+
base: data,
|
|
382
|
+
length: 1,
|
|
383
|
+
}
|
|
384
|
+
} else if (code === 'Q') {
|
|
385
|
+
// single quality value
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return mismatches.slice(0, j)
|
|
396
389
|
}
|
|
397
390
|
}
|
|
@@ -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,18 +459,18 @@ 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,
|
|
411
467
|
colorBy,
|
|
412
|
-
filterBy,
|
|
468
|
+
filterBy: JSON.parse(JSON.stringify(filterBy)),
|
|
413
469
|
colorTagMap: JSON.parse(JSON.stringify(colorTagMap)),
|
|
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,35 +7,51 @@ import { Tooltip } from '@jbrowse/plugin-wiggle'
|
|
|
7
7
|
type Count = {
|
|
8
8
|
[key: string]: {
|
|
9
9
|
total: number
|
|
10
|
-
|
|
10
|
+
'-1': number
|
|
11
|
+
'0': number
|
|
12
|
+
'1': number
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
type SNPInfo = {
|
|
15
|
-
ref: Count
|
|
16
17
|
cov: Count
|
|
17
18
|
lowqual: Count
|
|
18
19
|
noncov: Count
|
|
19
20
|
delskips: Count
|
|
21
|
+
refbase: string
|
|
20
22
|
total: number
|
|
23
|
+
ref: number
|
|
24
|
+
all: number
|
|
25
|
+
'-1': number
|
|
26
|
+
'0': number
|
|
27
|
+
'1': number
|
|
21
28
|
}
|
|
22
29
|
|
|
23
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)}%`
|
|
24
33
|
|
|
25
34
|
const TooltipContents = React.forwardRef(
|
|
26
|
-
({ feature }: { feature: Feature },
|
|
35
|
+
({ feature }: { feature: Feature }, reactRef: any) => {
|
|
27
36
|
const start = feature.get('start')
|
|
28
37
|
const end = feature.get('end')
|
|
29
38
|
const name = feature.get('refName')
|
|
30
|
-
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
|
|
31
49
|
const loc = [name, start === end ? en(start) : `${en(start)}..${en(end)}`]
|
|
32
50
|
.filter(f => !!f)
|
|
33
51
|
.join(':')
|
|
34
52
|
|
|
35
|
-
const total = info?.total
|
|
36
|
-
|
|
37
53
|
return (
|
|
38
|
-
<div ref={
|
|
54
|
+
<div ref={reactRef}>
|
|
39
55
|
<table>
|
|
40
56
|
<caption>{loc}</caption>
|
|
41
57
|
<thead>
|
|
@@ -50,33 +66,38 @@ const TooltipContents = React.forwardRef(
|
|
|
50
66
|
<tbody>
|
|
51
67
|
<tr>
|
|
52
68
|
<td>Total</td>
|
|
53
|
-
<td>{
|
|
69
|
+
<td>{all}</td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<td>REF {refbase ? `(${refbase.toUpperCase()})` : ''}</td>
|
|
73
|
+
<td>{ref}</td>
|
|
74
|
+
<td>{pct(ref, all)}</td>
|
|
75
|
+
<td>
|
|
76
|
+
{rn1 ? `${rn1}(-)` : ''}
|
|
77
|
+
{r1 ? `${r1}(+)` : ''}
|
|
78
|
+
</td>
|
|
54
79
|
<td />
|
|
55
80
|
</tr>
|
|
56
81
|
|
|
57
|
-
{Object.entries(info).map(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return (
|
|
82
|
+
{Object.entries(info as unknown as Record<string, Count>).map(
|
|
83
|
+
([key, entry]) =>
|
|
84
|
+
Object.entries(entry).map(([base, score]) => (
|
|
61
85
|
<tr key={base}>
|
|
62
86
|
<td>{base.toUpperCase()}</td>
|
|
63
87
|
<td>{score.total}</td>
|
|
64
88
|
<td>
|
|
65
89
|
{base === 'total' || base === 'skip'
|
|
66
90
|
? '---'
|
|
67
|
-
:
|
|
68
|
-
(score.total / (total || score.total || 1)) * 100,
|
|
69
|
-
)}%`}
|
|
91
|
+
: pct(score.total, all)}
|
|
70
92
|
</td>
|
|
71
93
|
<td>
|
|
72
|
-
{
|
|
73
|
-
{
|
|
94
|
+
{score['-1'] ? `${score['-1']}(-)` : ''}
|
|
95
|
+
{score['1'] ? `${score['1']}(+)` : ''}
|
|
74
96
|
</td>
|
|
75
97
|
<td>{key}</td>
|
|
76
98
|
</tr>
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
})}
|
|
99
|
+
)),
|
|
100
|
+
)}
|
|
80
101
|
</tbody>
|
|
81
102
|
</table>
|
|
82
103
|
</div>
|
|
@@ -143,7 +143,7 @@ const stateModelFactory = (
|
|
|
143
143
|
// must use getSnapshot because otherwise changes to e.g. just the
|
|
144
144
|
// colorBy.type are not read
|
|
145
145
|
colorBy: self.colorBy ? getSnapshot(self.colorBy) : undefined,
|
|
146
|
-
filterBy: self.filterBy,
|
|
146
|
+
filterBy: self.filterBy ? getSnapshot(self.filterBy) : undefined,
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
149
|
}
|
|
@@ -10,6 +10,7 @@ export interface PileupLayoutSessionProps {
|
|
|
10
10
|
config: AnyConfigurationModel
|
|
11
11
|
bpPerPx: number
|
|
12
12
|
filters: SerializableFilterChain
|
|
13
|
+
filterBy: unknown
|
|
13
14
|
sortedBy: unknown
|
|
14
15
|
showSoftClip: unknown
|
|
15
16
|
}
|
|
@@ -19,6 +20,7 @@ interface CachedPileupLayout {
|
|
|
19
20
|
layout: MyMultiLayout
|
|
20
21
|
config: AnyConfigurationModel
|
|
21
22
|
filters: SerializableFilterChain
|
|
23
|
+
filterBy: unknown
|
|
22
24
|
sortedBy: unknown
|
|
23
25
|
showSoftClip: boolean
|
|
24
26
|
}
|
|
@@ -26,6 +28,7 @@ interface CachedPileupLayout {
|
|
|
26
28
|
// Adds extra conditions to see if cached layout is valid
|
|
27
29
|
export class PileupLayoutSession extends LayoutSession {
|
|
28
30
|
sortedBy: unknown
|
|
31
|
+
filterBy: unknown
|
|
29
32
|
|
|
30
33
|
showSoftClip = false
|
|
31
34
|
|
|
@@ -38,7 +41,8 @@ export class PileupLayoutSession extends LayoutSession {
|
|
|
38
41
|
return (
|
|
39
42
|
super.cachedLayoutIsValid(cachedLayout) &&
|
|
40
43
|
this.showSoftClip === cachedLayout.showSoftClip &&
|
|
41
|
-
deepEqual(this.sortedBy, cachedLayout.sortedBy)
|
|
44
|
+
deepEqual(this.sortedBy, cachedLayout.sortedBy) &&
|
|
45
|
+
deepEqual(this.filterBy, cachedLayout.filterBy)
|
|
42
46
|
)
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -50,6 +54,7 @@ export class PileupLayoutSession extends LayoutSession {
|
|
|
50
54
|
layout: this.makeLayout(),
|
|
51
55
|
config: readConfObject(this.config),
|
|
52
56
|
filters: this.filters,
|
|
57
|
+
filterBy: this.filterBy,
|
|
53
58
|
sortedBy: this.sortedBy,
|
|
54
59
|
showSoftClip: this.showSoftClip,
|
|
55
60
|
}
|