jbrowse-plugin-mafviewer 1.1.3 → 1.2.0
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/BgzipTaffyAdapter/BgzipTaffyAdapter.d.ts +5 -4
- package/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.js +103 -82
- package/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.js.map +1 -1
- package/dist/BgzipTaffyAdapter/util.d.ts +1 -0
- package/dist/BgzipTaffyAdapter/util.js +22 -0
- package/dist/BgzipTaffyAdapter/util.js.map +1 -0
- package/dist/BigMafAdapter/BigMafAdapter.d.ts +3 -2
- package/dist/BigMafAdapter/BigMafAdapter.js +4 -3
- package/dist/BigMafAdapter/BigMafAdapter.js.map +1 -1
- package/dist/LinearMafDisplay/components/ColorLegend.js +1 -1
- package/dist/LinearMafDisplay/components/ColorLegend.js.map +1 -1
- package/dist/LinearMafDisplay/components/ReactComponent.js +1 -1
- package/dist/LinearMafDisplay/components/ReactComponent.js.map +1 -1
- package/dist/LinearMafDisplay/components/SvgWrapper.js +2 -2
- package/dist/LinearMafDisplay/components/SvgWrapper.js.map +1 -1
- package/dist/LinearMafDisplay/components/YScaleBars.js +2 -2
- package/dist/LinearMafDisplay/components/YScaleBars.js.map +1 -1
- package/dist/LinearMafDisplay/renderSvg.d.ts +2 -2
- package/dist/LinearMafDisplay/renderSvg.js +0 -1
- package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
- package/dist/LinearMafDisplay/stateModel.d.ts +19 -17
- package/dist/LinearMafDisplay/stateModel.js +17 -10
- package/dist/LinearMafDisplay/stateModel.js.map +1 -1
- package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +8 -4
- package/dist/LinearMafRenderer/LinearMafRenderer.js +14 -148
- package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
- package/dist/LinearMafRenderer/configSchema.d.ts +6 -1
- package/dist/LinearMafRenderer/configSchema.js +6 -1
- package/dist/LinearMafRenderer/configSchema.js.map +1 -1
- package/dist/LinearMafRenderer/makeImageData.d.ts +20 -0
- package/dist/LinearMafRenderer/makeImageData.js +144 -0
- package/dist/LinearMafRenderer/makeImageData.js.map +1 -0
- package/dist/LinearMafRenderer/util.d.ts +6 -1
- package/dist/LinearMafRenderer/util.js +17 -0
- package/dist/LinearMafRenderer/util.js.map +1 -1
- package/dist/MafAddTrackWorkflow/AddTrackWorkflow.d.ts +1 -1
- package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js.map +1 -1
- package/dist/MafTabixAdapter/MafTabixAdapter.d.ts +9 -6
- package/dist/MafTabixAdapter/MafTabixAdapter.js +74 -43
- package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
- package/dist/MafTabixAdapter/configSchema.d.ts +7 -0
- package/dist/MafTabixAdapter/configSchema.js +7 -0
- package/dist/MafTabixAdapter/configSchema.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +7 -8
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
- package/package.json +2 -2
- package/src/BgzipTaffyAdapter/BgzipTaffyAdapter.ts +123 -86
- package/src/BgzipTaffyAdapter/util.ts +25 -0
- package/src/BigMafAdapter/BigMafAdapter.ts +10 -4
- package/src/LinearMafDisplay/components/ColorLegend.tsx +1 -2
- package/src/LinearMafDisplay/components/ReactComponent.tsx +1 -1
- package/src/LinearMafDisplay/components/SvgWrapper.tsx +2 -2
- package/src/LinearMafDisplay/components/YScaleBars.tsx +3 -2
- package/src/LinearMafDisplay/renderSvg.tsx +5 -5
- package/src/LinearMafDisplay/stateModel.ts +30 -24
- package/src/LinearMafRenderer/LinearMafRenderer.ts +21 -176
- package/src/LinearMafRenderer/configSchema.ts +6 -1
- package/src/LinearMafRenderer/makeImageData.ts +211 -0
- package/src/LinearMafRenderer/util.ts +28 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +2 -1
- package/src/MafTabixAdapter/MafTabixAdapter.ts +92 -44
- package/src/MafTabixAdapter/configSchema.ts +7 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { FeatureRendererType } from '@jbrowse/core/pluggableElementTypes'
|
|
2
2
|
import { RenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
|
|
3
|
-
import { createJBrowseTheme } from '@jbrowse/core/ui'
|
|
4
3
|
import {
|
|
5
|
-
Feature,
|
|
6
4
|
Region,
|
|
7
|
-
featureSpanPx,
|
|
8
5
|
renderToAbstractCanvas,
|
|
6
|
+
updateStatus,
|
|
9
7
|
} from '@jbrowse/core/util'
|
|
10
8
|
|
|
11
|
-
import {
|
|
9
|
+
import { makeImageData } from './makeImageData'
|
|
12
10
|
|
|
13
11
|
interface Sample {
|
|
14
12
|
id: string
|
|
@@ -20,172 +18,9 @@ interface RenderArgs extends RenderArgsDeserialized {
|
|
|
20
18
|
rowProportion: number
|
|
21
19
|
showAllLetters: boolean
|
|
22
20
|
mismatchRendering: boolean
|
|
21
|
+
statusCallback?: (arg: string) => void
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
function makeImageData({
|
|
26
|
-
ctx,
|
|
27
|
-
renderArgs,
|
|
28
|
-
}: {
|
|
29
|
-
ctx: CanvasRenderingContext2D
|
|
30
|
-
renderArgs: RenderArgs & { features: Map<string, Feature> }
|
|
31
|
-
}) {
|
|
32
|
-
const {
|
|
33
|
-
regions,
|
|
34
|
-
bpPerPx,
|
|
35
|
-
rowHeight,
|
|
36
|
-
showAllLetters,
|
|
37
|
-
theme: configTheme,
|
|
38
|
-
mismatchRendering,
|
|
39
|
-
samples,
|
|
40
|
-
rowProportion,
|
|
41
|
-
features,
|
|
42
|
-
} = renderArgs
|
|
43
|
-
const region = regions[0]!
|
|
44
|
-
const h = rowHeight * rowProportion
|
|
45
|
-
const theme = createJBrowseTheme(configTheme)
|
|
46
|
-
const colorForBase = getColorBaseMap(theme)
|
|
47
|
-
const contrastForBase = getContrastBaseMap(theme)
|
|
48
|
-
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]))
|
|
49
|
-
const scale = 1 / bpPerPx
|
|
50
|
-
const f = 0.4
|
|
51
|
-
const h2 = rowHeight / 2
|
|
52
|
-
const hp2 = h / 2
|
|
53
|
-
const offset = (rowHeight - h) / 2
|
|
54
|
-
|
|
55
|
-
// sample as alignments
|
|
56
|
-
ctx.font = 'bold 10px Courier New,monospace'
|
|
57
|
-
|
|
58
|
-
for (const feature of features.values()) {
|
|
59
|
-
const [leftPx] = featureSpanPx(feature, region, bpPerPx)
|
|
60
|
-
const vals = feature.get('alignments') as Record<string, { data: string }>
|
|
61
|
-
const seq = feature.get('seq').toLowerCase()
|
|
62
|
-
for (const [sample, val] of Object.entries(vals)) {
|
|
63
|
-
const origAlignment = val.data
|
|
64
|
-
const alignment = origAlignment.toLowerCase()
|
|
65
|
-
|
|
66
|
-
const row = sampleToRowMap.get(sample)
|
|
67
|
-
if (row === undefined) {
|
|
68
|
-
continue
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const t = rowHeight * row
|
|
72
|
-
|
|
73
|
-
// gaps
|
|
74
|
-
ctx.beginPath()
|
|
75
|
-
ctx.fillStyle = 'black'
|
|
76
|
-
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
77
|
-
if (seq[i] !== '-') {
|
|
78
|
-
if (alignment[i] === '-') {
|
|
79
|
-
const l = leftPx + scale * o
|
|
80
|
-
ctx.moveTo(l, t + h2)
|
|
81
|
-
ctx.lineTo(l + scale + f, t + h2)
|
|
82
|
-
}
|
|
83
|
-
o++
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
ctx.stroke()
|
|
87
|
-
|
|
88
|
-
if (!showAllLetters) {
|
|
89
|
-
// matches
|
|
90
|
-
ctx.beginPath()
|
|
91
|
-
ctx.fillStyle = 'lightgrey'
|
|
92
|
-
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
93
|
-
if (seq[i] !== '-') {
|
|
94
|
-
const c = alignment[i]
|
|
95
|
-
const l = leftPx + scale * o
|
|
96
|
-
if (seq[i] === c && c !== '-') {
|
|
97
|
-
ctx.rect(l, offset + t, scale + f, h)
|
|
98
|
-
}
|
|
99
|
-
o++
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
ctx.fill()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// mismatches
|
|
106
|
-
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
107
|
-
const c = alignment[i]
|
|
108
|
-
if (seq[i] !== '-') {
|
|
109
|
-
if (c !== '-') {
|
|
110
|
-
const l = leftPx + scale * o
|
|
111
|
-
if (seq[i] !== c && c !== ' ') {
|
|
112
|
-
ctx.fillStyle = mismatchRendering
|
|
113
|
-
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
114
|
-
(colorForBase[c as keyof typeof colorForBase] ?? 'black')
|
|
115
|
-
: 'orange'
|
|
116
|
-
ctx.fillRect(l, offset + t, scale + f, h)
|
|
117
|
-
} else if (showAllLetters) {
|
|
118
|
-
ctx.fillStyle = mismatchRendering
|
|
119
|
-
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
120
|
-
(colorForBase[c as keyof typeof colorForBase] ?? 'black')
|
|
121
|
-
: 'lightblue'
|
|
122
|
-
ctx.fillRect(l, offset + t, scale + f, h)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
o++
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// font
|
|
130
|
-
const charSizeW = 10
|
|
131
|
-
if (scale >= charSizeW) {
|
|
132
|
-
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
133
|
-
if (seq[i] !== '-') {
|
|
134
|
-
const l = leftPx + scale * o
|
|
135
|
-
const offset = (scale - charSizeW) / 2 + 1
|
|
136
|
-
const c = alignment[i]!
|
|
137
|
-
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
138
|
-
ctx.fillStyle = mismatchRendering
|
|
139
|
-
? (contrastForBase[c] ?? 'white')
|
|
140
|
-
: 'black'
|
|
141
|
-
ctx.fillText(origAlignment[i] || '', l + offset, hp2 + t + 3)
|
|
142
|
-
}
|
|
143
|
-
o++
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// second pass for insertions, has slightly improved look since the
|
|
151
|
-
// insertions are always 'on top' of the other features
|
|
152
|
-
for (const feature of features.values()) {
|
|
153
|
-
const [leftPx] = featureSpanPx(feature, region, bpPerPx)
|
|
154
|
-
const vals = feature.get('alignments') as Record<string, { data: string }>
|
|
155
|
-
const seq = feature.get('seq').toLowerCase()
|
|
156
|
-
|
|
157
|
-
for (const [sample, val] of Object.entries(vals)) {
|
|
158
|
-
const origAlignment = val.data
|
|
159
|
-
const alignment = origAlignment.toLowerCase()
|
|
160
|
-
const row = sampleToRowMap.get(sample)
|
|
161
|
-
if (row === undefined) {
|
|
162
|
-
continue
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const t = rowHeight * row
|
|
166
|
-
|
|
167
|
-
ctx.beginPath()
|
|
168
|
-
ctx.fillStyle = 'purple'
|
|
169
|
-
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
170
|
-
let ins = ''
|
|
171
|
-
while (seq[i] === '-') {
|
|
172
|
-
if (alignment[i] !== '-' && alignment[i] !== ' ') {
|
|
173
|
-
ins += alignment[i]
|
|
174
|
-
}
|
|
175
|
-
i++
|
|
176
|
-
}
|
|
177
|
-
if (ins.length > 0) {
|
|
178
|
-
const l = leftPx + scale * o - 1
|
|
179
|
-
ctx.rect(l, offset + t + 1, 1, h - 1)
|
|
180
|
-
ctx.rect(l - 2, offset + t, 5, 1)
|
|
181
|
-
ctx.rect(l - 2, offset + t + h - 1, 5, 1)
|
|
182
|
-
}
|
|
183
|
-
o++
|
|
184
|
-
}
|
|
185
|
-
ctx.fill()
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
24
|
export default class LinearMafRenderer extends FeatureRendererType {
|
|
190
25
|
getExpandedRegion(region: Region) {
|
|
191
26
|
const { start, end } = region
|
|
@@ -199,7 +34,13 @@ export default class LinearMafRenderer extends FeatureRendererType {
|
|
|
199
34
|
}
|
|
200
35
|
}
|
|
201
36
|
async render(renderProps: RenderArgs) {
|
|
202
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
statusCallback = () => {},
|
|
39
|
+
regions,
|
|
40
|
+
bpPerPx,
|
|
41
|
+
samples,
|
|
42
|
+
rowHeight,
|
|
43
|
+
} = renderProps
|
|
203
44
|
const region = regions[0]!
|
|
204
45
|
const height = samples.length * (rowHeight + 1) + 100
|
|
205
46
|
const width = (region.end - region.start) / bpPerPx
|
|
@@ -208,13 +49,15 @@ export default class LinearMafRenderer extends FeatureRendererType {
|
|
|
208
49
|
width,
|
|
209
50
|
height,
|
|
210
51
|
renderProps,
|
|
211
|
-
ctx => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
52
|
+
async ctx => {
|
|
53
|
+
await updateStatus('Rendering alignment', statusCallback, () => {
|
|
54
|
+
makeImageData({
|
|
55
|
+
ctx,
|
|
56
|
+
renderArgs: {
|
|
57
|
+
...renderProps,
|
|
58
|
+
features,
|
|
59
|
+
},
|
|
60
|
+
})
|
|
218
61
|
})
|
|
219
62
|
return undefined
|
|
220
63
|
},
|
|
@@ -228,8 +71,10 @@ export default class LinearMafRenderer extends FeatureRendererType {
|
|
|
228
71
|
return {
|
|
229
72
|
...results,
|
|
230
73
|
...res,
|
|
74
|
+
features: new Map(),
|
|
231
75
|
width,
|
|
232
76
|
height,
|
|
77
|
+
containsNoTransferables: true,
|
|
233
78
|
}
|
|
234
79
|
}
|
|
235
80
|
}
|
|
@@ -7,7 +7,12 @@ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
|
7
7
|
|
|
8
8
|
const configSchema = ConfigurationSchema(
|
|
9
9
|
'LinearMafRenderer',
|
|
10
|
-
{
|
|
10
|
+
{
|
|
11
|
+
baseColor: {
|
|
12
|
+
type: 'color',
|
|
13
|
+
defaultValue: 'lightgrey',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
11
16
|
{
|
|
12
17
|
/**
|
|
13
18
|
* #baseConfiguration
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { RenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/renderers/BoxRendererType'
|
|
2
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui'
|
|
3
|
+
import { Feature, featureSpanPx } from '@jbrowse/core/util'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
fillRect,
|
|
7
|
+
getCharWidthHeight,
|
|
8
|
+
getColorBaseMap,
|
|
9
|
+
getContrastBaseMap,
|
|
10
|
+
} from './util'
|
|
11
|
+
|
|
12
|
+
interface Sample {
|
|
13
|
+
id: string
|
|
14
|
+
color?: string
|
|
15
|
+
}
|
|
16
|
+
interface RenderArgs extends RenderArgsDeserialized {
|
|
17
|
+
samples: Sample[]
|
|
18
|
+
rowHeight: number
|
|
19
|
+
rowProportion: number
|
|
20
|
+
showAllLetters: boolean
|
|
21
|
+
mismatchRendering: boolean
|
|
22
|
+
features: Map<string, Feature>
|
|
23
|
+
statusCallback?: (arg: string) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function makeImageData({
|
|
27
|
+
ctx,
|
|
28
|
+
renderArgs,
|
|
29
|
+
}: {
|
|
30
|
+
ctx: CanvasRenderingContext2D
|
|
31
|
+
renderArgs: RenderArgs
|
|
32
|
+
}) {
|
|
33
|
+
const {
|
|
34
|
+
regions,
|
|
35
|
+
bpPerPx,
|
|
36
|
+
rowHeight,
|
|
37
|
+
showAllLetters,
|
|
38
|
+
theme: configTheme,
|
|
39
|
+
mismatchRendering,
|
|
40
|
+
samples,
|
|
41
|
+
rowProportion,
|
|
42
|
+
features,
|
|
43
|
+
statusCallback,
|
|
44
|
+
} = renderArgs
|
|
45
|
+
const region = regions[0]!
|
|
46
|
+
const canvasWidth = (region.end - region.start) / bpPerPx
|
|
47
|
+
const h = rowHeight * rowProportion
|
|
48
|
+
const theme = createJBrowseTheme(configTheme)
|
|
49
|
+
const colorForBase = getColorBaseMap(theme)
|
|
50
|
+
const contrastForBase = getContrastBaseMap(theme)
|
|
51
|
+
|
|
52
|
+
const { charHeight } = getCharWidthHeight()
|
|
53
|
+
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]))
|
|
54
|
+
const scale = 1 / bpPerPx
|
|
55
|
+
const f = 0.4
|
|
56
|
+
const h2 = rowHeight / 2
|
|
57
|
+
const hp2 = h / 2
|
|
58
|
+
const offset = (rowHeight - h) / 2
|
|
59
|
+
|
|
60
|
+
// sample as alignments
|
|
61
|
+
ctx.font = 'bold 10px Courier New,monospace'
|
|
62
|
+
|
|
63
|
+
for (const feature of features.values()) {
|
|
64
|
+
const [leftPx] = featureSpanPx(feature, region, bpPerPx)
|
|
65
|
+
const vals = feature.get('alignments') as Record<string, { data: string }>
|
|
66
|
+
const seq = feature.get('seq').toLowerCase()
|
|
67
|
+
const r = Object.entries(vals)
|
|
68
|
+
for (const [sample, val] of r) {
|
|
69
|
+
const origAlignment = val.data
|
|
70
|
+
const alignment = origAlignment.toLowerCase()
|
|
71
|
+
|
|
72
|
+
const row = sampleToRowMap.get(sample)
|
|
73
|
+
if (row === undefined) {
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const t = rowHeight * row
|
|
78
|
+
|
|
79
|
+
// gaps
|
|
80
|
+
ctx.beginPath()
|
|
81
|
+
ctx.fillStyle = 'black'
|
|
82
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
83
|
+
if (seq[i] !== '-') {
|
|
84
|
+
if (alignment[i] === '-') {
|
|
85
|
+
const l = leftPx + scale * o
|
|
86
|
+
ctx.moveTo(l, t + h2)
|
|
87
|
+
ctx.lineTo(l + scale + f, t + h2)
|
|
88
|
+
}
|
|
89
|
+
o++
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
ctx.stroke()
|
|
93
|
+
|
|
94
|
+
if (!showAllLetters) {
|
|
95
|
+
// matches
|
|
96
|
+
ctx.fillStyle = 'lightgrey'
|
|
97
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
98
|
+
if (seq[i] !== '-') {
|
|
99
|
+
const c = alignment[i]
|
|
100
|
+
const l = leftPx + scale * o
|
|
101
|
+
if (seq[i] === c && c !== '-') {
|
|
102
|
+
fillRect(ctx, l, offset + t, scale + f, h, canvasWidth)
|
|
103
|
+
}
|
|
104
|
+
o++
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// mismatches
|
|
110
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
111
|
+
const c = alignment[i]
|
|
112
|
+
if (seq[i] !== '-') {
|
|
113
|
+
if (c !== '-') {
|
|
114
|
+
const l = leftPx + scale * o
|
|
115
|
+
if (seq[i] !== c && c !== ' ') {
|
|
116
|
+
fillRect(
|
|
117
|
+
ctx,
|
|
118
|
+
l,
|
|
119
|
+
offset + t,
|
|
120
|
+
scale + f,
|
|
121
|
+
h,
|
|
122
|
+
canvasWidth,
|
|
123
|
+
mismatchRendering
|
|
124
|
+
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
125
|
+
(colorForBase[c as keyof typeof colorForBase] ?? 'black')
|
|
126
|
+
: 'orange',
|
|
127
|
+
)
|
|
128
|
+
} else if (showAllLetters) {
|
|
129
|
+
fillRect(
|
|
130
|
+
ctx,
|
|
131
|
+
l,
|
|
132
|
+
offset + t,
|
|
133
|
+
scale + f,
|
|
134
|
+
h,
|
|
135
|
+
canvasWidth,
|
|
136
|
+
mismatchRendering
|
|
137
|
+
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
138
|
+
(colorForBase[c as keyof typeof colorForBase] ?? 'black')
|
|
139
|
+
: 'lightblue',
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
o++
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// font
|
|
148
|
+
const charSizeW = 10
|
|
149
|
+
if (scale >= charSizeW) {
|
|
150
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
151
|
+
if (seq[i] !== '-') {
|
|
152
|
+
const l = leftPx + scale * o
|
|
153
|
+
const offset = (scale - charSizeW) / 2 + 1
|
|
154
|
+
const c = alignment[i]!
|
|
155
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
156
|
+
ctx.fillStyle = mismatchRendering
|
|
157
|
+
? (contrastForBase[c] ?? 'white')
|
|
158
|
+
: 'black'
|
|
159
|
+
if (rowHeight > charHeight) {
|
|
160
|
+
ctx.fillText(origAlignment[i] || '', l + offset, hp2 + t + 3)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
o++
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// second pass for insertions, has slightly improved look since the
|
|
171
|
+
// insertions are always 'on top' of the other features
|
|
172
|
+
for (const feature of features.values()) {
|
|
173
|
+
const [leftPx] = featureSpanPx(feature, region, bpPerPx)
|
|
174
|
+
const vals = feature.get('alignments') as Record<string, { data: string }>
|
|
175
|
+
const seq = feature.get('seq').toLowerCase()
|
|
176
|
+
|
|
177
|
+
for (const [sample, val] of Object.entries(vals)) {
|
|
178
|
+
const origAlignment = val.data
|
|
179
|
+
const alignment = origAlignment.toLowerCase()
|
|
180
|
+
const row = sampleToRowMap.get(sample)
|
|
181
|
+
if (row === undefined) {
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const t = rowHeight * row
|
|
186
|
+
|
|
187
|
+
ctx.beginPath()
|
|
188
|
+
ctx.fillStyle = 'purple'
|
|
189
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
190
|
+
let ins = ''
|
|
191
|
+
while (seq[i] === '-') {
|
|
192
|
+
if (alignment[i] !== '-' && alignment[i] !== ' ') {
|
|
193
|
+
ins += alignment[i]
|
|
194
|
+
}
|
|
195
|
+
i++
|
|
196
|
+
}
|
|
197
|
+
if (ins.length > 0) {
|
|
198
|
+
const l = leftPx + scale * o - 1
|
|
199
|
+
|
|
200
|
+
ctx.rect(l, offset + t, 1, h)
|
|
201
|
+
if (bpPerPx < 1) {
|
|
202
|
+
ctx.rect(l - 2, offset + t, 5, 1)
|
|
203
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
o++
|
|
207
|
+
}
|
|
208
|
+
ctx.fill()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { measureText } from '@jbrowse/core/util'
|
|
2
|
+
import type { Theme } from '@mui/material'
|
|
2
3
|
|
|
3
4
|
export function getContrastBaseMap(theme: Theme) {
|
|
4
5
|
return Object.fromEntries(
|
|
@@ -18,3 +19,29 @@ export function getColorBaseMap(theme: Theme) {
|
|
|
18
19
|
t: bases.T.main,
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
|
|
23
|
+
export function fillRect(
|
|
24
|
+
ctx: CanvasRenderingContext2D,
|
|
25
|
+
l: number,
|
|
26
|
+
t: number,
|
|
27
|
+
w: number,
|
|
28
|
+
h: number,
|
|
29
|
+
cw: number,
|
|
30
|
+
color?: string,
|
|
31
|
+
) {
|
|
32
|
+
if (l + w < 0 || l > cw) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if (color) {
|
|
36
|
+
ctx.fillStyle = color
|
|
37
|
+
}
|
|
38
|
+
ctx.fillRect(l, t, w, h)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// get width and height of chars the height is an approximation: width letter M
|
|
42
|
+
// is approximately the height
|
|
43
|
+
export function getCharWidthHeight() {
|
|
44
|
+
const charWidth = measureText('A')
|
|
45
|
+
const charHeight = measureText('M') - 2
|
|
46
|
+
return { charWidth, charHeight }
|
|
47
|
+
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
isSessionModelWithWidgets,
|
|
8
8
|
isSessionWithAddTracks,
|
|
9
9
|
} from '@jbrowse/core/util'
|
|
10
|
-
import { AddTrackModel } from '@jbrowse/plugin-data-management'
|
|
11
10
|
import {
|
|
12
11
|
Button,
|
|
13
12
|
FormControl,
|
|
@@ -21,6 +20,8 @@ import {
|
|
|
21
20
|
import { getRoot } from 'mobx-state-tree'
|
|
22
21
|
import { makeStyles } from 'tss-react/mui'
|
|
23
22
|
|
|
23
|
+
import type { AddTrackModel } from '@jbrowse/plugin-data-management'
|
|
24
|
+
|
|
24
25
|
const useStyles = makeStyles()(theme => ({
|
|
25
26
|
textbox: {
|
|
26
27
|
width: '100%',
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
BaseFeatureDataAdapter,
|
|
3
|
+
BaseOptions,
|
|
4
|
+
} from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
5
|
+
import {
|
|
6
|
+
Feature,
|
|
7
|
+
Region,
|
|
8
|
+
SimpleFeature,
|
|
9
|
+
updateStatus,
|
|
10
|
+
} from '@jbrowse/core/util'
|
|
3
11
|
import { openLocation } from '@jbrowse/core/util/io'
|
|
4
12
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
5
13
|
import { getSnapshot } from 'mobx-state-tree'
|
|
@@ -20,7 +28,7 @@ interface OrganismRecord {
|
|
|
20
28
|
export default class MafTabixAdapter extends BaseFeatureDataAdapter {
|
|
21
29
|
public setupP?: Promise<{ adapter: BaseFeatureDataAdapter }>
|
|
22
30
|
|
|
23
|
-
async
|
|
31
|
+
async setupPre() {
|
|
24
32
|
const config = this.config
|
|
25
33
|
if (!this.getSubAdapter) {
|
|
26
34
|
throw new Error('no getSubAdapter available')
|
|
@@ -33,9 +41,9 @@ export default class MafTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
33
41
|
adapter: adapter.dataAdapter as BaseFeatureDataAdapter,
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
|
-
async
|
|
44
|
+
async setupPre2() {
|
|
37
45
|
if (!this.setupP) {
|
|
38
|
-
this.setupP = this.
|
|
46
|
+
this.setupP = this.setupPre().catch((e: unknown) => {
|
|
39
47
|
this.setupP = undefined
|
|
40
48
|
throw e
|
|
41
49
|
})
|
|
@@ -43,60 +51,100 @@ export default class MafTabixAdapter extends BaseFeatureDataAdapter {
|
|
|
43
51
|
return this.setupP
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
async
|
|
47
|
-
const {
|
|
54
|
+
async setup(opts?: BaseOptions) {
|
|
55
|
+
const { statusCallback = () => {} } = opts || {}
|
|
56
|
+
return updateStatus('Downloading index', statusCallback, () =>
|
|
57
|
+
this.setupPre2(),
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getRefNames(opts?: BaseOptions) {
|
|
62
|
+
const { adapter } = await this.setup(opts)
|
|
48
63
|
return adapter.getRefNames()
|
|
49
64
|
}
|
|
50
65
|
|
|
51
|
-
async getHeader() {
|
|
52
|
-
const { adapter } = await this.setup()
|
|
66
|
+
async getHeader(opts?: BaseOptions) {
|
|
67
|
+
const { adapter } = await this.setup(opts)
|
|
53
68
|
return adapter.getHeader()
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
getFeatures(query: Region) {
|
|
71
|
+
getFeatures(query: Region, opts?: BaseOptions) {
|
|
72
|
+
const { statusCallback = () => {} } = opts || {}
|
|
57
73
|
return ObservableCreate<Feature>(async observer => {
|
|
58
|
-
const { adapter } = await this.setup()
|
|
59
|
-
const features = await
|
|
60
|
-
|
|
74
|
+
const { adapter } = await this.setup(opts)
|
|
75
|
+
const features = await updateStatus(
|
|
76
|
+
'Downloading alignments',
|
|
77
|
+
statusCallback,
|
|
78
|
+
() => firstValueFrom(adapter.getFeatures(query).pipe(toArray())),
|
|
61
79
|
)
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
81
|
+
await updateStatus('Processing alignments', statusCallback, () => {
|
|
82
|
+
let firstAssemblyNameFound = ''
|
|
83
|
+
const refAssemblyName = this.getConf('refAssemblyName')
|
|
84
|
+
for (const feature of features) {
|
|
85
|
+
const data = (feature.get('field5') as string).split(',')
|
|
86
|
+
const alignments = {} as Record<string, OrganismRecord>
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
for (let j = 0; j < data.length; j++) {
|
|
89
|
+
const elt = data[j]!
|
|
90
|
+
const seq = elt.split(':')[5]!
|
|
91
|
+
const ad = elt.split(':')
|
|
92
|
+
const ag = ad[0]!.split('.')
|
|
93
|
+
const [n1, n2 = '', ...rest] = ag
|
|
94
|
+
let assemblyName
|
|
95
|
+
let last = ''
|
|
96
|
+
if (ag.length === 2) {
|
|
97
|
+
assemblyName = n1
|
|
98
|
+
last = n2!
|
|
99
|
+
} else if (!Number.isNaN(+n2)) {
|
|
100
|
+
assemblyName = `${n1}.${n2}`
|
|
101
|
+
last = rest.join('.')
|
|
102
|
+
} else {
|
|
103
|
+
assemblyName = n1
|
|
104
|
+
last = [n2, ...rest].join('.')
|
|
105
|
+
}
|
|
106
|
+
if (assemblyName) {
|
|
107
|
+
firstAssemblyNameFound = firstAssemblyNameFound || assemblyName
|
|
108
|
+
alignments[assemblyName] = {
|
|
109
|
+
chr: last,
|
|
110
|
+
start: +ad[1]!,
|
|
111
|
+
srcSize: +ad[2]!,
|
|
112
|
+
strand: ad[3] === '-' ? -1 : 1,
|
|
113
|
+
unknown: +ad[4]!,
|
|
114
|
+
data: seq,
|
|
115
|
+
}
|
|
81
116
|
}
|
|
82
117
|
}
|
|
83
|
-
}
|
|
84
118
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
id: feature.id(),
|
|
88
|
-
data: {
|
|
89
|
-
start: feature.get('start'),
|
|
90
|
-
end: feature.get('end'),
|
|
91
|
-
refName: feature.get('refName'),
|
|
92
|
-
name: feature.get('name'),
|
|
93
|
-
score: feature.get('score'),
|
|
119
|
+
console.log(
|
|
120
|
+
{
|
|
94
121
|
alignments,
|
|
95
|
-
|
|
122
|
+
firstAssemblyNameFound,
|
|
123
|
+
refAssemblyName,
|
|
124
|
+
q: query.assemblyName,
|
|
96
125
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
alignments[refAssemblyName],
|
|
127
|
+
alignments[query.assemblyName],
|
|
128
|
+
alignments[firstAssemblyNameFound],
|
|
129
|
+
)
|
|
130
|
+
observer.next(
|
|
131
|
+
new SimpleFeature({
|
|
132
|
+
id: feature.id(),
|
|
133
|
+
data: {
|
|
134
|
+
start: feature.get('start'),
|
|
135
|
+
end: feature.get('end'),
|
|
136
|
+
refName: feature.get('refName'),
|
|
137
|
+
name: feature.get('name'),
|
|
138
|
+
score: feature.get('score'),
|
|
139
|
+
alignments,
|
|
140
|
+
seq:
|
|
141
|
+
alignments[refAssemblyName || query.assemblyName]?.data ||
|
|
142
|
+
alignments[firstAssemblyNameFound]?.data,
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
100
148
|
observer.complete()
|
|
101
149
|
})
|
|
102
150
|
}
|