jbrowse-plugin-mafviewer 1.0.2 → 1.0.4
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/CHANGELOG.md +3 -0
- package/README.md +183 -0
- package/dist/jbrowse-plugin-mafviewer.umd.development.js +239 -136
- package/dist/jbrowse-plugin-mafviewer.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +1 -1
- package/package.json +8 -10
- package/src/BigMafAdapter/BigMafAdapter.ts +8 -18
- package/src/LinearMafDisplay/components/ColorLegend.tsx +27 -28
- package/src/LinearMafDisplay/components/ReactComponent.tsx +4 -1
- package/src/LinearMafDisplay/components/SetRowHeight.tsx +76 -0
- package/src/LinearMafDisplay/components/YScaleBars.tsx +7 -3
- package/src/LinearMafDisplay/configSchema.ts +2 -3
- package/src/LinearMafDisplay/renderSvg.tsx +13 -4
- package/src/LinearMafDisplay/stateModel.ts +83 -20
- package/src/LinearMafRenderer/LinearMafRenderer.ts +110 -50
- package/src/LinearMafRenderer/components/ReactComponent.tsx +4 -1
- package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/MafTabixAdapter/MafTabixAdapter.ts +11 -20
|
@@ -43,8 +43,8 @@ export const YScaleBars = observer(function (props: {
|
|
|
43
43
|
}) {
|
|
44
44
|
const { model } = props
|
|
45
45
|
const { rowHeight, samples } = model
|
|
46
|
-
const svgFontSize = Math.min(rowHeight,
|
|
47
|
-
const canDisplayLabel = rowHeight
|
|
46
|
+
const svgFontSize = Math.min(Math.max(rowHeight, 10), 14)
|
|
47
|
+
const canDisplayLabel = rowHeight >= 10
|
|
48
48
|
const minWidth = 20
|
|
49
49
|
|
|
50
50
|
const labelWidth = Math.max(
|
|
@@ -55,7 +55,11 @@ export const YScaleBars = observer(function (props: {
|
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
57
|
<Wrapper {...props}>
|
|
58
|
-
<ColorLegend
|
|
58
|
+
<ColorLegend
|
|
59
|
+
model={model}
|
|
60
|
+
labelWidth={labelWidth}
|
|
61
|
+
svgFontSize={svgFontSize}
|
|
62
|
+
/>
|
|
59
63
|
</Wrapper>
|
|
60
64
|
)
|
|
61
65
|
})
|
|
@@ -5,8 +5,7 @@ export default function configSchemaF(pluginManager: PluginManager) {
|
|
|
5
5
|
const LinearGenomePlugin = pluginManager.getPlugin(
|
|
6
6
|
'LinearGenomeViewPlugin',
|
|
7
7
|
) as import('@jbrowse/plugin-linear-genome-view').default
|
|
8
|
-
|
|
9
|
-
const { linearBasicDisplayConfigSchemaFactory } = LinearGenomePlugin.exports
|
|
8
|
+
const { baseLinearDisplayConfigSchema } = LinearGenomePlugin.exports
|
|
10
9
|
return ConfigurationSchema(
|
|
11
10
|
'LinearMafDisplay',
|
|
12
11
|
{
|
|
@@ -16,7 +15,7 @@ export default function configSchemaF(pluginManager: PluginManager) {
|
|
|
16
15
|
renderer: pluginManager.pluggableConfigSchemaType('renderer'),
|
|
17
16
|
},
|
|
18
17
|
{
|
|
19
|
-
baseConfiguration:
|
|
18
|
+
baseConfiguration: baseLinearDisplayConfigSchema,
|
|
20
19
|
explicitlyTyped: true,
|
|
21
20
|
},
|
|
22
21
|
)
|
|
@@ -14,12 +14,21 @@ export async function renderSvg(
|
|
|
14
14
|
opts: ExportSvgDisplayOptions,
|
|
15
15
|
superRenderSvg: (opts: ExportSvgDisplayOptions) => Promise<React.ReactNode>,
|
|
16
16
|
) {
|
|
17
|
-
const {
|
|
17
|
+
const { height, id } = self
|
|
18
|
+
const { offsetPx, width } = getContainingView(self) as LinearGenomeViewModel
|
|
19
|
+
const clipid = `mafclip-${id}`
|
|
18
20
|
return (
|
|
19
21
|
<>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
<defs>
|
|
23
|
+
<clipPath id={clipid}>
|
|
24
|
+
<rect x={0} y={0} width={width} height={height} />
|
|
25
|
+
</clipPath>
|
|
26
|
+
</defs>
|
|
27
|
+
<g clipPath={`url(#${clipid})`}>
|
|
28
|
+
<g id="snpcov">{await superRenderSvg(opts)}</g>
|
|
29
|
+
<g transform={`translate(${Math.max(-offsetPx, 0)})`}>
|
|
30
|
+
<YScaleBars model={self} orientation="left" exportSVG />
|
|
31
|
+
</g>
|
|
23
32
|
</g>
|
|
24
33
|
</>
|
|
25
34
|
)
|
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
ConfigurationReference,
|
|
6
6
|
getConf,
|
|
7
7
|
} from '@jbrowse/core/configuration'
|
|
8
|
-
import { getEnv } from '@jbrowse/core/util'
|
|
8
|
+
import { getEnv, getSession } from '@jbrowse/core/util'
|
|
9
9
|
import PluginManager from '@jbrowse/core/PluginManager'
|
|
10
10
|
import { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view'
|
|
11
|
+
import SetRowHeightDialog from './components/SetRowHeight'
|
|
11
12
|
|
|
12
13
|
function isStrs(array: unknown[]): array is string[] {
|
|
13
14
|
return typeof array[0] === 'string'
|
|
@@ -24,13 +25,12 @@ export default function stateModelFactory(
|
|
|
24
25
|
const LinearGenomePlugin = pluginManager.getPlugin(
|
|
25
26
|
'LinearGenomeViewPlugin',
|
|
26
27
|
) as import('@jbrowse/plugin-linear-genome-view').default
|
|
27
|
-
|
|
28
|
-
const { linearBasicDisplayModelFactory } = LinearGenomePlugin.exports
|
|
28
|
+
const { BaseLinearDisplay } = LinearGenomePlugin.exports
|
|
29
29
|
|
|
30
30
|
return types
|
|
31
31
|
.compose(
|
|
32
32
|
'LinearMafDisplay',
|
|
33
|
-
|
|
33
|
+
BaseLinearDisplay,
|
|
34
34
|
types.model({
|
|
35
35
|
/**
|
|
36
36
|
* #property
|
|
@@ -40,11 +40,43 @@ export default function stateModelFactory(
|
|
|
40
40
|
* #property
|
|
41
41
|
*/
|
|
42
42
|
configuration: ConfigurationReference(configSchema),
|
|
43
|
+
/**
|
|
44
|
+
* #property
|
|
45
|
+
*/
|
|
46
|
+
rowHeight: 15,
|
|
47
|
+
/**
|
|
48
|
+
* #property
|
|
49
|
+
*/
|
|
50
|
+
rowProportion: 0.8,
|
|
51
|
+
/**
|
|
52
|
+
* #property
|
|
53
|
+
*/
|
|
54
|
+
showAllLetters: false,
|
|
43
55
|
}),
|
|
44
56
|
)
|
|
45
57
|
.volatile(() => ({
|
|
46
58
|
prefersOffset: true,
|
|
47
59
|
}))
|
|
60
|
+
.actions(self => ({
|
|
61
|
+
/**
|
|
62
|
+
* #action
|
|
63
|
+
*/
|
|
64
|
+
setRowHeight(n: number) {
|
|
65
|
+
self.rowHeight = n
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* #action
|
|
69
|
+
*/
|
|
70
|
+
setRowProportion(n: number) {
|
|
71
|
+
self.rowProportion = n
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* #action
|
|
75
|
+
*/
|
|
76
|
+
setShowAllLetters(f: boolean) {
|
|
77
|
+
self.showAllLetters = f
|
|
78
|
+
},
|
|
79
|
+
}))
|
|
48
80
|
.views(self => ({
|
|
49
81
|
/**
|
|
50
82
|
* #getter
|
|
@@ -53,18 +85,11 @@ export default function stateModelFactory(
|
|
|
53
85
|
const r = self.adapterConfig.samples as
|
|
54
86
|
| string[]
|
|
55
87
|
| { id: string; label: string; color?: string }[]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return r
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
/**
|
|
63
|
-
* #getter
|
|
64
|
-
*/
|
|
65
|
-
get rowHeight() {
|
|
66
|
-
return 20
|
|
88
|
+
return isStrs(r)
|
|
89
|
+
? r.map(elt => ({ id: elt, label: elt, color: undefined }))
|
|
90
|
+
: r
|
|
67
91
|
},
|
|
92
|
+
|
|
68
93
|
/**
|
|
69
94
|
* #getter
|
|
70
95
|
*/
|
|
@@ -88,29 +113,67 @@ export default function stateModelFactory(
|
|
|
88
113
|
},
|
|
89
114
|
}))
|
|
90
115
|
.views(self => {
|
|
91
|
-
const {
|
|
116
|
+
const {
|
|
117
|
+
trackMenuItems: superTrackMenuItems,
|
|
118
|
+
renderProps: superRenderProps,
|
|
119
|
+
} = self
|
|
92
120
|
return {
|
|
93
121
|
/**
|
|
94
122
|
* #method
|
|
95
123
|
*/
|
|
96
124
|
renderProps() {
|
|
125
|
+
const {
|
|
126
|
+
showAllLetters,
|
|
127
|
+
rendererConfig,
|
|
128
|
+
samples,
|
|
129
|
+
rowHeight,
|
|
130
|
+
rowProportion,
|
|
131
|
+
} = self
|
|
97
132
|
return {
|
|
98
133
|
...superRenderProps(),
|
|
99
|
-
|
|
100
|
-
|
|
134
|
+
config: rendererConfig,
|
|
135
|
+
samples,
|
|
136
|
+
rowHeight,
|
|
137
|
+
rowProportion,
|
|
138
|
+
showAllLetters,
|
|
101
139
|
}
|
|
102
140
|
},
|
|
141
|
+
/**
|
|
142
|
+
* #method
|
|
143
|
+
*/
|
|
144
|
+
trackMenuItems() {
|
|
145
|
+
return [
|
|
146
|
+
...superTrackMenuItems(),
|
|
147
|
+
{
|
|
148
|
+
label: 'Set row height',
|
|
149
|
+
onClick: () => {
|
|
150
|
+
getSession(self).queueDialog(handleClose => [
|
|
151
|
+
SetRowHeightDialog,
|
|
152
|
+
{ model: self, handleClose },
|
|
153
|
+
])
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: 'Show all letters',
|
|
158
|
+
type: 'checkbox',
|
|
159
|
+
checked: self.showAllLetters,
|
|
160
|
+
onClick: () => {
|
|
161
|
+
self.setShowAllLetters(!self.showAllLetters)
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
},
|
|
103
166
|
}
|
|
104
167
|
})
|
|
105
168
|
.actions(self => {
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
106
170
|
const { renderSvg: superRenderSvg } = self
|
|
107
171
|
return {
|
|
108
172
|
/**
|
|
109
173
|
* #action
|
|
110
174
|
*/
|
|
111
|
-
async renderSvg(opts: ExportSvgDisplayOptions)
|
|
175
|
+
async renderSvg(opts: ExportSvgDisplayOptions) {
|
|
112
176
|
const { renderSvg } = await import('./renderSvg')
|
|
113
|
-
// @ts-expect-error
|
|
114
177
|
return renderSvg(self, opts, superRenderSvg)
|
|
115
178
|
},
|
|
116
179
|
}
|
|
@@ -3,23 +3,12 @@ import { RenderArgsDeserialized } from '@jbrowse/core/pluggableElementTypes/rend
|
|
|
3
3
|
import { createJBrowseTheme } from '@jbrowse/core/ui'
|
|
4
4
|
import {
|
|
5
5
|
Feature,
|
|
6
|
+
Region,
|
|
6
7
|
featureSpanPx,
|
|
7
8
|
renderToAbstractCanvas,
|
|
8
9
|
} from '@jbrowse/core/util'
|
|
9
10
|
import { Theme } from '@mui/material'
|
|
10
11
|
|
|
11
|
-
function getCorrectionFactor(scale: number) {
|
|
12
|
-
if (scale >= 1) {
|
|
13
|
-
return 0.6
|
|
14
|
-
} else if (scale >= 0.2) {
|
|
15
|
-
return 0.05
|
|
16
|
-
} else if (scale >= 0.02) {
|
|
17
|
-
return 0.03
|
|
18
|
-
} else {
|
|
19
|
-
return 0.02
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
12
|
export function getContrastBaseMap(theme: Theme) {
|
|
24
13
|
return Object.fromEntries(
|
|
25
14
|
Object.entries(getColorBaseMap(theme)).map(([key, value]) => [
|
|
@@ -46,24 +35,31 @@ function makeImageData({
|
|
|
46
35
|
renderArgs: RenderArgsDeserialized & {
|
|
47
36
|
samples: { id: string; color?: string }[]
|
|
48
37
|
rowHeight: number
|
|
38
|
+
rowProportion: number
|
|
39
|
+
showAllLetters: boolean
|
|
49
40
|
}
|
|
50
41
|
}) {
|
|
51
42
|
const {
|
|
52
43
|
regions,
|
|
53
44
|
bpPerPx,
|
|
54
45
|
rowHeight,
|
|
46
|
+
showAllLetters,
|
|
55
47
|
theme: configTheme,
|
|
56
48
|
samples,
|
|
49
|
+
rowProportion,
|
|
57
50
|
} = renderArgs
|
|
58
51
|
const [region] = regions
|
|
59
52
|
const features = renderArgs.features as Map<string, Feature>
|
|
60
|
-
const h = rowHeight
|
|
53
|
+
const h = rowHeight * rowProportion
|
|
61
54
|
const theme = createJBrowseTheme(configTheme)
|
|
62
55
|
const colorForBase = getColorBaseMap(theme)
|
|
63
56
|
const contrastForBase = getContrastBaseMap(theme)
|
|
64
57
|
const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]))
|
|
65
58
|
const scale = 1 / bpPerPx
|
|
66
|
-
const
|
|
59
|
+
const f = 0.4
|
|
60
|
+
const h2 = rowHeight / 2
|
|
61
|
+
const hp2 = h / 2
|
|
62
|
+
const offset = (rowHeight - h) / 2
|
|
67
63
|
|
|
68
64
|
// sample as alignments
|
|
69
65
|
ctx.font = 'bold 10px Courier New,monospace'
|
|
@@ -76,75 +72,139 @@ function makeImageData({
|
|
|
76
72
|
const origAlignment = val.data
|
|
77
73
|
const alignment = origAlignment.toLowerCase()
|
|
78
74
|
|
|
79
|
-
// gaps
|
|
80
|
-
ctx.beginPath()
|
|
81
|
-
ctx.fillStyle = 'black'
|
|
82
|
-
const offset0 = (5 / 12) * h
|
|
83
|
-
const h6 = h / 6
|
|
84
75
|
const row = sampleToRowMap.get(sample)
|
|
85
76
|
if (row === undefined) {
|
|
86
77
|
throw new Error(`unknown sample encountered: ${sample}`)
|
|
87
78
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
|
|
80
|
+
const t = rowHeight * row
|
|
81
|
+
|
|
82
|
+
// gaps
|
|
83
|
+
ctx.beginPath()
|
|
84
|
+
ctx.fillStyle = 'black'
|
|
85
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
86
|
+
if (seq[i] !== '-') {
|
|
87
|
+
if (alignment[i] === '-') {
|
|
88
|
+
const l = leftPx + scale * o
|
|
89
|
+
ctx.moveTo(l, t + h2)
|
|
90
|
+
ctx.lineTo(l + scale + f, t + h2)
|
|
91
|
+
}
|
|
92
|
+
o++
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
ctx.
|
|
96
|
-
const offset = (1 / 4) * h
|
|
97
|
-
const h2 = h / 2
|
|
95
|
+
ctx.stroke()
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
if (!showAllLetters) {
|
|
98
|
+
// matches
|
|
99
|
+
ctx.beginPath()
|
|
100
|
+
ctx.fillStyle = 'lightgrey'
|
|
101
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
102
|
+
if (seq[i] !== '-') {
|
|
103
|
+
const c = alignment[i]
|
|
104
|
+
const l = leftPx + scale * o
|
|
105
|
+
if (seq[i] === c && c !== '-') {
|
|
106
|
+
ctx.rect(l, offset + t, scale + f, h)
|
|
107
|
+
}
|
|
108
|
+
o++
|
|
109
|
+
}
|
|
107
110
|
}
|
|
111
|
+
ctx.fill()
|
|
108
112
|
}
|
|
109
|
-
ctx.fill()
|
|
110
113
|
|
|
111
114
|
// mismatches
|
|
112
|
-
for (let i = 0; i < alignment.length; i++) {
|
|
115
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
113
116
|
const c = alignment[i]
|
|
114
|
-
if (seq[i] !==
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
if (seq[i] !== '-') {
|
|
118
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
119
|
+
const l = leftPx + scale * o
|
|
120
|
+
ctx.fillStyle =
|
|
121
|
+
colorForBase[c as keyof typeof colorForBase] ?? 'black'
|
|
122
|
+
ctx.fillRect(l, offset + t, scale + f, h)
|
|
123
|
+
}
|
|
124
|
+
o++
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
// font
|
|
123
129
|
const charSize = { w: 10 }
|
|
124
130
|
if (scale >= charSize.w) {
|
|
125
|
-
for (let i = 0; i < alignment.length; i++) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
132
|
+
if (seq[i] !== '-') {
|
|
133
|
+
const l = leftPx + scale * o
|
|
134
|
+
const offset = (scale - charSize.w) / 2 + 1
|
|
135
|
+
const c = alignment[i]
|
|
136
|
+
if ((showAllLetters || seq[i] !== c) && c !== '-') {
|
|
137
|
+
ctx.fillStyle = contrastForBase[c] ?? 'white'
|
|
138
|
+
ctx.fillText(origAlignment[i], l + offset, hp2 + t + 3)
|
|
139
|
+
}
|
|
140
|
+
o++
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
}
|
|
146
|
+
|
|
147
|
+
// second pass for insertions, has slightly improved look since the
|
|
148
|
+
// insertions are always 'on top' of the other features
|
|
149
|
+
for (const feature of features.values()) {
|
|
150
|
+
const [leftPx] = featureSpanPx(feature, region, bpPerPx)
|
|
151
|
+
const vals = feature.get('alignments') as Record<string, { data: string }>
|
|
152
|
+
const seq = feature.get('seq').toLowerCase()
|
|
153
|
+
|
|
154
|
+
for (const [sample, val] of Object.entries(vals)) {
|
|
155
|
+
const origAlignment = val.data
|
|
156
|
+
const alignment = origAlignment.toLowerCase()
|
|
157
|
+
const row = sampleToRowMap.get(sample)
|
|
158
|
+
if (row === undefined) {
|
|
159
|
+
throw new Error(`unknown sample encountered: ${sample}`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const t = rowHeight * row
|
|
163
|
+
|
|
164
|
+
ctx.beginPath()
|
|
165
|
+
ctx.fillStyle = 'purple'
|
|
166
|
+
for (let i = 0, o = 0; i < alignment.length; i++) {
|
|
167
|
+
let ins = ''
|
|
168
|
+
while (seq[i] === '-') {
|
|
169
|
+
if (alignment[i] !== '-') {
|
|
170
|
+
ins += alignment[i]
|
|
171
|
+
}
|
|
172
|
+
i++
|
|
173
|
+
}
|
|
174
|
+
if (ins.length > 0) {
|
|
175
|
+
const l = leftPx + scale * o - 1
|
|
176
|
+
ctx.rect(l, offset + t + 1, 1, h - 1)
|
|
177
|
+
ctx.rect(l - 2, offset + t, 5, 1)
|
|
178
|
+
ctx.rect(l - 2, offset + t + h - 1, 5, 1)
|
|
179
|
+
}
|
|
180
|
+
o++
|
|
181
|
+
}
|
|
182
|
+
ctx.fill()
|
|
183
|
+
}
|
|
184
|
+
}
|
|
137
185
|
}
|
|
138
186
|
export default class LinearMafRenderer extends FeatureRendererType {
|
|
187
|
+
getExpandedRegion(region: Region) {
|
|
188
|
+
const { start, end } = region
|
|
189
|
+
const bpExpansion = 1
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
193
|
+
...(region as Omit<typeof region, symbol>),
|
|
194
|
+
start: Math.floor(Math.max(start - bpExpansion, 0)),
|
|
195
|
+
end: Math.ceil(end + bpExpansion),
|
|
196
|
+
}
|
|
197
|
+
}
|
|
139
198
|
async render(
|
|
140
199
|
renderProps: RenderArgsDeserialized & {
|
|
141
200
|
samples: { id: string; color?: string }[]
|
|
142
201
|
rowHeight: number
|
|
202
|
+
rowProportion: number
|
|
143
203
|
},
|
|
144
204
|
) {
|
|
145
205
|
const { regions, bpPerPx, samples, rowHeight } = renderProps
|
|
146
206
|
const [region] = regions
|
|
147
|
-
const height = samples.length * rowHeight
|
|
207
|
+
const height = samples.length * rowHeight + 100
|
|
148
208
|
const width = (region.end - region.start) / bpPerPx
|
|
149
209
|
const features = await this.getFeatures(renderProps)
|
|
150
210
|
const res = await renderToAbstractCanvas(width, height, renderProps, ctx =>
|
|
@@ -2,7 +2,10 @@ import { PrerenderedCanvas } from '@jbrowse/core/ui'
|
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
|
|
5
|
-
const LinearMafRendering = observer(function (props:
|
|
5
|
+
const LinearMafRendering = observer(function (props: {
|
|
6
|
+
width: number
|
|
7
|
+
height: number
|
|
8
|
+
}) {
|
|
6
9
|
return <PrerenderedCanvas {...props} />
|
|
7
10
|
})
|
|
8
11
|
|
|
@@ -43,6 +43,7 @@ export default function MultiMAFWidget({ model }: { model: AddTrackModel }) {
|
|
|
43
43
|
const [error, setError] = useState<unknown>()
|
|
44
44
|
const [trackName, setTrackName] = useState('MAF track')
|
|
45
45
|
const [choice, setChoice] = useState('BigMafAdapter')
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
47
|
const rootModel = getRoot<any>(model)
|
|
47
48
|
return (
|
|
48
49
|
<Paper className={classes.paper}>
|
|
@@ -43,6 +43,11 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
43
43
|
return adapter.getRefNames()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
async getHeader() {
|
|
47
|
+
const { adapter } = await this.setup()
|
|
48
|
+
return adapter.getHeader()
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
getFeatures(query: Region) {
|
|
47
52
|
return ObservableCreate<Feature>(async observer => {
|
|
48
53
|
const { adapter } = await this.setup()
|
|
@@ -52,33 +57,19 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
52
57
|
for (const feature of features) {
|
|
53
58
|
const data = (feature.get('field5') as string).split(',')
|
|
54
59
|
const alignments = {} as Record<string, OrganismRecord>
|
|
55
|
-
const main = data[0]
|
|
56
|
-
const aln = main.split(':')[5]
|
|
57
60
|
const alns = data.map(elt => elt.split(':')[5])
|
|
58
|
-
const alns2 = data.map(() => '')
|
|
59
|
-
// remove extraneous data in other alignments
|
|
60
|
-
// reason being: cannot represent missing data in main species that are in others)
|
|
61
|
-
for (let i = 0, o = 0; i < aln.length; i++, o++) {
|
|
62
|
-
if (aln[i] !== '-') {
|
|
63
|
-
for (let j = 0; j < data.length; j++) {
|
|
64
|
-
alns2[j] += alns[j][i]
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
for (let j = 0; j < data.length; j++) {
|
|
69
|
-
const elt = data[j]
|
|
70
61
|
|
|
62
|
+
for (const [j, elt] of data.entries()) {
|
|
71
63
|
const ad = elt.split(':')
|
|
72
|
-
const org = ad[0].split('.')
|
|
73
|
-
const chr = ad[0].split('.')[1]
|
|
64
|
+
const [org, chr] = ad[0].split('.')
|
|
74
65
|
|
|
75
66
|
alignments[org] = {
|
|
76
|
-
chr
|
|
67
|
+
chr,
|
|
77
68
|
start: +ad[1],
|
|
78
69
|
srcSize: +ad[2],
|
|
79
70
|
strand: ad[3] === '-' ? -1 : 1,
|
|
80
71
|
unknown: +ad[4],
|
|
81
|
-
data:
|
|
72
|
+
data: alns[j],
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
75
|
|
|
@@ -91,8 +82,8 @@ export default class BigMafAdapter extends BaseFeatureDataAdapter {
|
|
|
91
82
|
refName: feature.get('refName'),
|
|
92
83
|
name: feature.get('name'),
|
|
93
84
|
score: feature.get('score'),
|
|
94
|
-
alignments
|
|
95
|
-
seq:
|
|
85
|
+
alignments,
|
|
86
|
+
seq: alns[0],
|
|
96
87
|
},
|
|
97
88
|
}),
|
|
98
89
|
)
|