@jbrowse/plugin-alignments 1.6.6 → 1.6.9
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/BamAdapter/BamSlightlyLazyFeature.d.ts +1 -10
- package/dist/BamAdapter/MismatchParser.d.ts +3 -5
- package/dist/CramAdapter/CramSlightlyLazyFeature.d.ts +1 -2
- package/dist/LinearSNPCoverageDisplay/models/model.d.ts +2 -2
- package/dist/PileupRenderer/PileupRenderer.d.ts +20 -6
- package/dist/SNPCoverageAdapter/SNPCoverageAdapter.d.ts +3 -11
- package/dist/plugin-alignments.cjs.development.js +591 -552
- package/dist/plugin-alignments.cjs.development.js.map +1 -1
- package/dist/plugin-alignments.cjs.production.min.js +1 -1
- package/dist/plugin-alignments.cjs.production.min.js.map +1 -1
- package/dist/plugin-alignments.esm.js +594 -555
- package/dist/plugin-alignments.esm.js.map +1 -1
- package/dist/util.d.ts +4 -0
- package/package.json +3 -3
- package/src/BamAdapter/BamAdapter.ts +10 -7
- package/src/BamAdapter/BamSlightlyLazyFeature.ts +11 -79
- package/src/BamAdapter/MismatchParser.test.ts +53 -297
- package/src/BamAdapter/MismatchParser.ts +54 -116
- package/src/BamAdapter/configSchema.ts +0 -4
- package/src/CramAdapter/CramSlightlyLazyFeature.ts +3 -10
- package/src/LinearAlignmentsDisplay/models/model.tsx +4 -6
- package/src/LinearPileupDisplay/components/ColorByModifications.tsx +76 -80
- package/src/LinearPileupDisplay/components/ColorByTag.tsx +24 -23
- package/src/LinearPileupDisplay/components/FilterByTag.tsx +73 -68
- package/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +28 -26
- package/src/LinearPileupDisplay/components/SetMaxHeight.tsx +24 -13
- package/src/LinearPileupDisplay/components/SortByTag.tsx +29 -21
- package/src/LinearPileupDisplay/model.ts +6 -0
- package/src/PileupRenderer/PileupRenderer.tsx +178 -57
- package/src/SNPCoverageAdapter/SNPCoverageAdapter.ts +180 -229
- package/src/SNPCoverageRenderer/SNPCoverageRenderer.ts +12 -11
- package/src/util.ts +25 -0
|
@@ -9,63 +9,81 @@ export interface Mismatch {
|
|
|
9
9
|
seq?: string
|
|
10
10
|
cliplen?: number
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const mdRegex = new RegExp(/(\d+|\^[a-z]+|[a-z])/gi)
|
|
13
13
|
export function parseCigar(cigar: string) {
|
|
14
14
|
return (cigar || '').split(/([MIDNSHPX=])/)
|
|
15
15
|
}
|
|
16
16
|
export function cigarToMismatches(
|
|
17
17
|
ops: string[],
|
|
18
18
|
seq: string,
|
|
19
|
+
ref?: string,
|
|
19
20
|
qual?: Buffer,
|
|
20
21
|
): Mismatch[] {
|
|
21
|
-
let
|
|
22
|
-
let
|
|
22
|
+
let roffset = 0 // reference offset
|
|
23
|
+
let soffset = 0 // seq offset
|
|
23
24
|
const mismatches: Mismatch[] = []
|
|
24
|
-
|
|
25
|
+
const hasRefAndSeq = ref && seq
|
|
26
|
+
for (let i = 0; i < ops.length; i += 2) {
|
|
25
27
|
const len = +ops[i]
|
|
26
28
|
const op = ops[i + 1]
|
|
29
|
+
|
|
27
30
|
if (op === 'M' || op === '=' || op === 'E') {
|
|
28
|
-
|
|
31
|
+
if (hasRefAndSeq) {
|
|
32
|
+
for (let j = 0; j < len; j++) {
|
|
33
|
+
if (
|
|
34
|
+
// @ts-ignore in the full yarn build of the repo, this says that object is possibly undefined for some reason, ignored
|
|
35
|
+
seq[soffset + j].toUpperCase() !== ref[roffset + j].toUpperCase()
|
|
36
|
+
) {
|
|
37
|
+
mismatches.push({
|
|
38
|
+
start: roffset + j,
|
|
39
|
+
type: 'mismatch',
|
|
40
|
+
base: seq[soffset + j],
|
|
41
|
+
length: 1,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
soffset += len
|
|
29
47
|
}
|
|
30
48
|
if (op === 'I') {
|
|
31
49
|
mismatches.push({
|
|
32
|
-
start:
|
|
50
|
+
start: roffset,
|
|
33
51
|
type: 'insertion',
|
|
34
52
|
base: `${len}`,
|
|
35
53
|
length: 0,
|
|
36
54
|
})
|
|
37
|
-
|
|
55
|
+
soffset += len
|
|
38
56
|
} else if (op === 'D') {
|
|
39
57
|
mismatches.push({
|
|
40
|
-
start:
|
|
58
|
+
start: roffset,
|
|
41
59
|
type: 'deletion',
|
|
42
60
|
base: '*',
|
|
43
61
|
length: len,
|
|
44
62
|
})
|
|
45
63
|
} else if (op === 'N') {
|
|
46
64
|
mismatches.push({
|
|
47
|
-
start:
|
|
65
|
+
start: roffset,
|
|
48
66
|
type: 'skip',
|
|
49
67
|
base: 'N',
|
|
50
68
|
length: len,
|
|
51
69
|
})
|
|
52
70
|
} else if (op === 'X') {
|
|
53
|
-
const r = seq.slice(
|
|
54
|
-
const q = qual?.slice(
|
|
71
|
+
const r = seq.slice(soffset, soffset + len)
|
|
72
|
+
const q = qual?.slice(soffset, soffset + len) || []
|
|
55
73
|
|
|
56
74
|
for (let j = 0; j < len; j++) {
|
|
57
75
|
mismatches.push({
|
|
58
|
-
start:
|
|
76
|
+
start: roffset + j,
|
|
59
77
|
type: 'mismatch',
|
|
60
78
|
base: r[j],
|
|
61
79
|
qual: q[j],
|
|
62
80
|
length: 1,
|
|
63
81
|
})
|
|
64
82
|
}
|
|
65
|
-
|
|
83
|
+
soffset += len
|
|
66
84
|
} else if (op === 'H') {
|
|
67
85
|
mismatches.push({
|
|
68
|
-
start:
|
|
86
|
+
start: roffset,
|
|
69
87
|
type: 'hardclip',
|
|
70
88
|
base: `H${len}`,
|
|
71
89
|
cliplen: len,
|
|
@@ -73,17 +91,17 @@ export function cigarToMismatches(
|
|
|
73
91
|
})
|
|
74
92
|
} else if (op === 'S') {
|
|
75
93
|
mismatches.push({
|
|
76
|
-
start:
|
|
94
|
+
start: roffset,
|
|
77
95
|
type: 'softclip',
|
|
78
96
|
base: `S${len}`,
|
|
79
97
|
cliplen: len,
|
|
80
98
|
length: 1,
|
|
81
99
|
})
|
|
82
|
-
|
|
100
|
+
soffset += len
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
if (op !== 'I' && op !== 'S' && op !== 'H') {
|
|
86
|
-
|
|
104
|
+
roffset += len
|
|
87
105
|
}
|
|
88
106
|
}
|
|
89
107
|
return mismatches
|
|
@@ -95,7 +113,7 @@ export function cigarToMismatches(
|
|
|
95
113
|
*/
|
|
96
114
|
export function mdToMismatches(
|
|
97
115
|
mdstring: string,
|
|
98
|
-
|
|
116
|
+
ops: string[],
|
|
99
117
|
cigarMismatches: Mismatch[],
|
|
100
118
|
seq: string,
|
|
101
119
|
qual?: Buffer,
|
|
@@ -129,11 +147,12 @@ export function mdToMismatches(
|
|
|
129
147
|
let refOffset = lastRefOffset
|
|
130
148
|
for (
|
|
131
149
|
let i = lastCigar;
|
|
132
|
-
i <
|
|
150
|
+
i < ops.length && refOffset <= refCoord;
|
|
133
151
|
i += 2, lastCigar = i
|
|
134
152
|
) {
|
|
135
|
-
const len = +
|
|
136
|
-
const op =
|
|
153
|
+
const len = +ops[i]
|
|
154
|
+
const op = ops[i + 1]
|
|
155
|
+
|
|
137
156
|
if (op === 'S' || op === 'I') {
|
|
138
157
|
templateOffset += len
|
|
139
158
|
} else if (op === 'D' || op === 'P' || op === 'N') {
|
|
@@ -150,18 +169,14 @@ export function mdToMismatches(
|
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
// now actually parse the MD string
|
|
153
|
-
const md = mdstring.match(
|
|
172
|
+
const md = mdstring.match(mdRegex) || []
|
|
154
173
|
for (let i = 0; i < md.length; i++) {
|
|
155
174
|
const token = md[i]
|
|
156
175
|
const num = +token
|
|
157
176
|
if (!Number.isNaN(num)) {
|
|
158
177
|
curr.start += num
|
|
159
178
|
} else if (token.startsWith('^')) {
|
|
160
|
-
curr.
|
|
161
|
-
curr.base = '*'
|
|
162
|
-
curr.type = 'deletion'
|
|
163
|
-
curr.seq = token.substring(1)
|
|
164
|
-
nextRecord()
|
|
179
|
+
curr.start += token.length - 1
|
|
165
180
|
} else {
|
|
166
181
|
// mismatch
|
|
167
182
|
for (let j = 0; j < token.length; j += 1) {
|
|
@@ -176,9 +191,9 @@ export function mdToMismatches(
|
|
|
176
191
|
break
|
|
177
192
|
}
|
|
178
193
|
}
|
|
179
|
-
const s =
|
|
180
|
-
curr.base = seq
|
|
181
|
-
const qualScore = qual?.
|
|
194
|
+
const s = getTemplateCoordLocal(curr.start)
|
|
195
|
+
curr.base = seq[s] || 'X'
|
|
196
|
+
const qualScore = qual?.[s]
|
|
182
197
|
if (qualScore) {
|
|
183
198
|
curr.qual = qualScore
|
|
184
199
|
}
|
|
@@ -190,106 +205,30 @@ export function mdToMismatches(
|
|
|
190
205
|
return mismatchRecords
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
export function getTemplateCoord(refCoord: number, cigarOps: string[]): number {
|
|
194
|
-
let templateOffset = 0
|
|
195
|
-
let refOffset = 0
|
|
196
|
-
for (let i = 0; i < cigarOps.length && refOffset <= refCoord; i += 2) {
|
|
197
|
-
const len = +cigarOps[i]
|
|
198
|
-
const op = cigarOps[i + 1]
|
|
199
|
-
if (op === 'S' || op === 'I') {
|
|
200
|
-
templateOffset += len
|
|
201
|
-
} else if (op === 'D' || op === 'P') {
|
|
202
|
-
refOffset += len
|
|
203
|
-
} else if (op !== 'H') {
|
|
204
|
-
templateOffset += len
|
|
205
|
-
refOffset += len
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return templateOffset - (refOffset - refCoord)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
208
|
export function getMismatches(
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
cigar: string,
|
|
210
|
+
md: string,
|
|
214
211
|
seq: string,
|
|
212
|
+
ref?: string,
|
|
215
213
|
qual?: Buffer,
|
|
216
214
|
): Mismatch[] {
|
|
217
215
|
let mismatches: Mismatch[] = []
|
|
218
|
-
|
|
216
|
+
const ops = parseCigar(cigar)
|
|
219
217
|
|
|
220
218
|
// parse the CIGAR tag if it has one
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
mismatches = mismatches.concat(cigarToMismatches(cigarOps, seq, qual))
|
|
219
|
+
if (cigar) {
|
|
220
|
+
mismatches = mismatches.concat(cigarToMismatches(ops, seq, ref, qual))
|
|
224
221
|
}
|
|
225
222
|
|
|
226
223
|
// now let's look for CRAM or MD mismatches
|
|
227
|
-
if (
|
|
224
|
+
if (md) {
|
|
228
225
|
mismatches = mismatches.concat(
|
|
229
|
-
mdToMismatches(
|
|
226
|
+
mdToMismatches(md, ops, mismatches, seq, qual),
|
|
230
227
|
)
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
const seen: { [index: string]: boolean } = {}
|
|
235
|
-
return mismatches.filter(m => {
|
|
236
|
-
const key = `${m.type},${m.start},${m.length}`
|
|
237
|
-
const s = seen[key]
|
|
238
|
-
seen[key] = true
|
|
239
|
-
return !s
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// adapted from minimap2 code static void write_MD_core function
|
|
244
|
-
export function generateMD(target: string, query: string, cigar: string) {
|
|
245
|
-
let queryOffset = 0
|
|
246
|
-
let targetOffset = 0
|
|
247
|
-
let lengthMD = 0
|
|
248
|
-
if (!target) {
|
|
249
|
-
console.warn('no ref supplied to generateMD')
|
|
250
|
-
return ''
|
|
251
|
-
}
|
|
252
|
-
const cigarOps = parseCigar(cigar)
|
|
253
|
-
let str = ''
|
|
254
|
-
for (let i = 0; i < cigarOps.length; i += 2) {
|
|
255
|
-
const len = +cigarOps[i]
|
|
256
|
-
const op = cigarOps[i + 1]
|
|
257
|
-
if (op === 'M' || op === 'X' || op === '=') {
|
|
258
|
-
for (let j = 0; j < len; j++) {
|
|
259
|
-
if (
|
|
260
|
-
query[queryOffset + j].toLowerCase() !==
|
|
261
|
-
target[targetOffset + j].toLowerCase()
|
|
262
|
-
) {
|
|
263
|
-
str += `${lengthMD}${target[targetOffset + j].toUpperCase()}`
|
|
264
|
-
lengthMD = 0
|
|
265
|
-
} else {
|
|
266
|
-
lengthMD++
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
queryOffset += len
|
|
270
|
-
targetOffset += len
|
|
271
|
-
} else if (op === 'I') {
|
|
272
|
-
queryOffset += len
|
|
273
|
-
} else if (op === 'D') {
|
|
274
|
-
let tmp = ''
|
|
275
|
-
for (let j = 0; j < len; j++) {
|
|
276
|
-
tmp += target[targetOffset + j].toUpperCase()
|
|
277
|
-
}
|
|
278
|
-
str += `${lengthMD}^${tmp}`
|
|
279
|
-
lengthMD = 0
|
|
280
|
-
targetOffset += len
|
|
281
|
-
} else if (op === 'N') {
|
|
282
|
-
targetOffset += len
|
|
283
|
-
} else if (op === 'S') {
|
|
284
|
-
queryOffset += len
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (lengthMD > 0) {
|
|
288
|
-
str += lengthMD
|
|
289
|
-
}
|
|
290
|
-
return str
|
|
230
|
+
return mismatches
|
|
291
231
|
}
|
|
292
|
-
|
|
293
232
|
// get relative reference sequence positions for positions given relative to
|
|
294
233
|
// the read sequence
|
|
295
234
|
export function* getNextRefPos(cigarOps: string[], positions: number[]) {
|
|
@@ -315,7 +254,6 @@ export function* getNextRefPos(cigarOps: string[], positions: number[]) {
|
|
|
315
254
|
yield positions[i] - readPos + refPos
|
|
316
255
|
}
|
|
317
256
|
}
|
|
318
|
-
|
|
319
257
|
export function getModificationPositions(
|
|
320
258
|
mm: string,
|
|
321
259
|
fseq: string,
|
|
@@ -18,12 +18,10 @@ export interface Mismatch {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export default class CramSlightlyLazyFeature implements Feature {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// uses parameter properties to automatically create fields on the class
|
|
22
|
+
// https://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
|
-
constructor(private record: any,
|
|
25
|
-
this._store = store
|
|
26
|
-
}
|
|
24
|
+
constructor(private record: any, private _store: CramAdapter) {}
|
|
27
25
|
|
|
28
26
|
_get_name() {
|
|
29
27
|
return this.record.readName
|
|
@@ -232,7 +230,6 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
232
230
|
prop =>
|
|
233
231
|
prop.startsWith('_get_') &&
|
|
234
232
|
prop !== '_get_mismatches' &&
|
|
235
|
-
prop !== '_get_skips_and_dels' &&
|
|
236
233
|
prop !== '_get_cram_read_features',
|
|
237
234
|
)
|
|
238
235
|
.map(methodName => methodName.replace('_get_', ''))
|
|
@@ -397,8 +394,4 @@ export default class CramSlightlyLazyFeature implements Feature {
|
|
|
397
394
|
)
|
|
398
395
|
return mismatches
|
|
399
396
|
}
|
|
400
|
-
|
|
401
|
-
_get_skips_and_dels(): Mismatch[] {
|
|
402
|
-
return this._get_mismatches()
|
|
403
|
-
}
|
|
404
397
|
}
|
|
@@ -225,12 +225,10 @@ const stateModelFactory = (
|
|
|
225
225
|
<>
|
|
226
226
|
<g>{await self.SNPCoverageDisplay.renderSvg(opts)}</g>
|
|
227
227
|
<g transform={`translate(0 ${self.SNPCoverageDisplay.height})`}>
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
})
|
|
233
|
-
}
|
|
228
|
+
{await self.PileupDisplay.renderSvg({
|
|
229
|
+
...opts,
|
|
230
|
+
overrideHeight: pileupHeight,
|
|
231
|
+
})}
|
|
234
232
|
</g>
|
|
235
233
|
</>
|
|
236
234
|
)
|
|
@@ -4,6 +4,7 @@ import { ObservableMap } from 'mobx'
|
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
7
|
+
DialogActions,
|
|
7
8
|
DialogContent,
|
|
8
9
|
DialogTitle,
|
|
9
10
|
IconButton,
|
|
@@ -14,14 +15,12 @@ import {
|
|
|
14
15
|
import CloseIcon from '@material-ui/icons/Close'
|
|
15
16
|
|
|
16
17
|
const useStyles = makeStyles(theme => ({
|
|
17
|
-
root: {},
|
|
18
18
|
closeButton: {
|
|
19
19
|
position: 'absolute',
|
|
20
20
|
right: theme.spacing(1),
|
|
21
21
|
top: theme.spacing(1),
|
|
22
22
|
color: theme.palette.grey[500],
|
|
23
23
|
},
|
|
24
|
-
|
|
25
24
|
table: {
|
|
26
25
|
border: '1px solid #888',
|
|
27
26
|
margin: theme.spacing(4),
|
|
@@ -84,85 +83,82 @@ function ColorByTagDlg(props: {
|
|
|
84
83
|
</IconButton>
|
|
85
84
|
</DialogTitle>
|
|
86
85
|
<DialogContent>
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
/>
|
|
127
|
-
) : null}
|
|
128
|
-
</div>
|
|
129
|
-
<div style={{ display: 'flex' }}>
|
|
130
|
-
<Button
|
|
131
|
-
variant="contained"
|
|
132
|
-
color="primary"
|
|
133
|
-
style={{ margin: 5 }}
|
|
134
|
-
onClick={() => {
|
|
135
|
-
model.setColorScheme({
|
|
136
|
-
type: 'modifications',
|
|
137
|
-
})
|
|
138
|
-
handleClose()
|
|
139
|
-
}}
|
|
140
|
-
>
|
|
141
|
-
Modifications
|
|
142
|
-
</Button>
|
|
143
|
-
<Button
|
|
144
|
-
variant="contained"
|
|
145
|
-
color="primary"
|
|
146
|
-
style={{ margin: 5 }}
|
|
147
|
-
onClick={() => {
|
|
148
|
-
model.setColorScheme({
|
|
149
|
-
type: 'methylation',
|
|
150
|
-
})
|
|
151
|
-
handleClose()
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
Methylation
|
|
155
|
-
</Button>
|
|
156
|
-
<Button
|
|
157
|
-
variant="contained"
|
|
158
|
-
color="secondary"
|
|
159
|
-
style={{ margin: 5 }}
|
|
160
|
-
onClick={() => handleClose()}
|
|
161
|
-
>
|
|
162
|
-
Cancel
|
|
163
|
-
</Button>
|
|
164
|
-
</div>
|
|
86
|
+
<Typography>
|
|
87
|
+
You can choose to color the modifications in the BAM/CRAM MM/ML
|
|
88
|
+
specification using this dialog. Choosing modifications colors the
|
|
89
|
+
modified positions and can color multiple modification types. Choosing
|
|
90
|
+
the methylation setting colors methylated and unmethylated CpG.
|
|
91
|
+
</Typography>
|
|
92
|
+
<Typography>
|
|
93
|
+
Note: you can revisit this dialog to see the current mapping of colors
|
|
94
|
+
to modification type for the modification coloring mode
|
|
95
|
+
</Typography>
|
|
96
|
+
<div style={{ margin: 20 }}>
|
|
97
|
+
{colorBy?.type === 'modifications' ? (
|
|
98
|
+
<div>
|
|
99
|
+
{modifications.length ? (
|
|
100
|
+
<>
|
|
101
|
+
Current modification-type-to-color mapping
|
|
102
|
+
<ModificationTable
|
|
103
|
+
modifications={[...modificationTagMap.entries()]}
|
|
104
|
+
/>
|
|
105
|
+
</>
|
|
106
|
+
) : (
|
|
107
|
+
<div>
|
|
108
|
+
<Typography>
|
|
109
|
+
Note: color by modifications is already enabled. Loading
|
|
110
|
+
current modifications...
|
|
111
|
+
</Typography>
|
|
112
|
+
<CircularProgress size={15} />
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
) : null}
|
|
117
|
+
{colorBy?.type === 'methylation' ? (
|
|
118
|
+
<ModificationTable
|
|
119
|
+
modifications={[
|
|
120
|
+
['methylated', 'red'],
|
|
121
|
+
['unmethylated', 'blue'],
|
|
122
|
+
]}
|
|
123
|
+
/>
|
|
124
|
+
) : null}
|
|
165
125
|
</div>
|
|
126
|
+
<DialogActions>
|
|
127
|
+
<Button
|
|
128
|
+
variant="contained"
|
|
129
|
+
color="primary"
|
|
130
|
+
style={{ margin: 5 }}
|
|
131
|
+
onClick={() => {
|
|
132
|
+
model.setColorScheme({
|
|
133
|
+
type: 'modifications',
|
|
134
|
+
})
|
|
135
|
+
handleClose()
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
Modifications
|
|
139
|
+
</Button>
|
|
140
|
+
<Button
|
|
141
|
+
variant="contained"
|
|
142
|
+
color="primary"
|
|
143
|
+
style={{ margin: 5 }}
|
|
144
|
+
onClick={() => {
|
|
145
|
+
model.setColorScheme({
|
|
146
|
+
type: 'methylation',
|
|
147
|
+
})
|
|
148
|
+
handleClose()
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
Methylation
|
|
152
|
+
</Button>
|
|
153
|
+
<Button
|
|
154
|
+
variant="contained"
|
|
155
|
+
color="secondary"
|
|
156
|
+
style={{ margin: 5 }}
|
|
157
|
+
onClick={() => handleClose()}
|
|
158
|
+
>
|
|
159
|
+
Cancel
|
|
160
|
+
</Button>
|
|
161
|
+
</DialogActions>
|
|
166
162
|
</DialogContent>
|
|
167
163
|
</Dialog>
|
|
168
164
|
)
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Button,
|
|
5
5
|
Dialog,
|
|
6
6
|
DialogContent,
|
|
7
|
+
DialogActions,
|
|
7
8
|
DialogTitle,
|
|
8
9
|
IconButton,
|
|
9
10
|
TextField,
|
|
@@ -46,32 +47,29 @@ function ColorByTagDlg(props: {
|
|
|
46
47
|
</IconButton>
|
|
47
48
|
</DialogTitle>
|
|
48
49
|
<DialogContent style={{ overflowX: 'hidden' }}>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</Typography>
|
|
50
|
+
<Typography>Enter tag to color by: </Typography>
|
|
51
|
+
<Typography color="textSecondary">
|
|
52
|
+
Examples: XS or TS for RNA-seq inferred read strand, ts (lower-case)
|
|
53
|
+
for minimap2 read strand, HP for haplotype, RG for read group, etc.
|
|
54
|
+
</Typography>
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/>
|
|
56
|
+
<TextField
|
|
57
|
+
value={tag}
|
|
58
|
+
onChange={event => setTag(event.target.value)}
|
|
59
|
+
placeholder="Enter tag name"
|
|
60
|
+
inputProps={{
|
|
61
|
+
maxLength: 2,
|
|
62
|
+
'data-testid': 'color-tag-name-input',
|
|
63
|
+
}}
|
|
64
|
+
error={tag.length === 2 && !validTag}
|
|
65
|
+
helperText={tag.length === 2 && !validTag ? 'Not a valid tag' : ''}
|
|
66
|
+
autoComplete="off"
|
|
67
|
+
data-testid="color-tag-name"
|
|
68
|
+
/>
|
|
69
|
+
<DialogActions>
|
|
71
70
|
<Button
|
|
72
71
|
variant="contained"
|
|
73
72
|
color="primary"
|
|
74
|
-
style={{ marginLeft: 20 }}
|
|
75
73
|
onClick={() => {
|
|
76
74
|
model.setColorScheme({
|
|
77
75
|
type: 'tag',
|
|
@@ -83,7 +81,10 @@ function ColorByTagDlg(props: {
|
|
|
83
81
|
>
|
|
84
82
|
Submit
|
|
85
83
|
</Button>
|
|
86
|
-
|
|
84
|
+
<Button variant="contained" color="secondary" onClick={handleClose}>
|
|
85
|
+
Cancel
|
|
86
|
+
</Button>
|
|
87
|
+
</DialogActions>
|
|
87
88
|
</DialogContent>
|
|
88
89
|
</Dialog>
|
|
89
90
|
)
|